GraphQL 既是一種用於 API 的查詢語言也是一個滿足你數據查詢的運行時。GraphQL 對你的 API 中的數據提供了一套易於理解的完整描述,使得客戶端能夠準確地獲得它需要的數據,而且沒有任何冗餘,也讓 API 更容易地隨着時間推移而演進,還能用於構建強大的開發者工具。
——出自 https://graphql.cn
對於查詢,更多的時候,數據是在結構化數據庫中,API服務通過ORM實現查詢數據庫,並且API以不同的url提供給外部調用;試想,我們如果通過ado.net來訪問數據庫的話,對於GraphQL的靈活查詢方式,我們怎麼通過一條語句完全適配?這是個難點,只能把全部的數據集查詢出來,讓graphql在內存篩選自己所需要的數據,這樣的話,大量數據的集合很快就會把內存佔完的,不可取,當然像dapper這種直接寫sql的方式就不行了。
這時,EF的優勢就顯露出來了,其實EF本身是給後臺程序員使用,封裝了一組Linq表達式轉sql的功能,這樣後臺程序員就不用關心sql語句了;這裏,如果能把GraphQL和Linq打通,就可以實現GraphQL接口,後臺開發也變的簡單了;正好,天作一對,GraphQL碰上了EF,使兩者變的“天衣無縫”。
Michael Staib也是這麼做的,並且帶來了HotChocolate,下面是一個GraphQL+EF(sql server)的案例。
添加Nuget包
HotChocolate.AspNetCore
HotChocolate.Data
HotChocolate.Data.EntityFramework
using HotChocolate;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace GraphQLDemo01
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddPooledDbContextFactory<AdventureWorks2016Context>(
(services, options) => options
.UseSqlServer(Configuration.GetConnectionString("ConnectionString"))
.UseLoggerFactory(services.GetRequiredService<ILoggerFactory>()))
.AddGraphQLServer()
.AddQueryType<Query>()
.AddFiltering()
.AddSorting()
.AddProjections();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapGraphQL();
});
}
}
}
案例數據庫是用的sql server官方的demo數據庫AdventureWorks,每個查詢方法變的非常簡單,只需要把對應實體集合返回即可,但返回值一定是IQueryabl<>,正是這個特點,讓GraphQL與EF變的這麼貼合。特性上增加了一個分頁,考慮到數據量大,HotChocolate很貼心的帶了分頁。AdventureWorks生成的實體類和Context就不作顯示了。
using System.Linq;
using HotChocolate;
using HotChocolate.Data;
using HotChocolate.Types;
namespace GraphQLDemo01
{
public class Query
{
[UseDbContext(typeof(AdventureWorks2016Context))]
[UseOffsetPaging]
[UseProjection]
[UseFiltering]
[UseSorting]
public IQueryable<Product> GetProducts([ScopedService] AdventureWorks2016Context context)
{
return context.Products;
}
[UseDbContext(typeof(AdventureWorks2016Context))]
[UsePaging]
[UseProjection]
[UseFiltering]
[UseSorting]
public IQueryable<Person> GetPersons([ScopedService] AdventureWorks2016Context context)
{
return context.People;
}
}
}
使用查詢色彩爲紅色的產品,並且按listPrice排序
{
products(where: { color:{ eq:"Red"} } order:[{listPrice:ASC}]) {
items{
productId
name
listPrice
}
}
}
分頁(UsePaging)查詢person
{
persons( order: [{ businessEntityId: ASC }] after:"MTk="){
pageInfo{
hasNextPage
hasPreviousPage
startCursor
endCursor
}
nodes{
businessEntityId
firstName
middleName
lastName
emailAddresses{
emailAddressId
emailAddress1
modifiedDate
}
}
edges{
cursor
node{
businessEntityId
}
}
}
}
分頁(UseOffsetPaging)查詢產品
{
products( order: [{ productId: ASC }] skip:40 take:20){
pageInfo{
hasNextPage
hasPreviousPage
}
items{
productId
name
}
}
}
這些查詢如果你跟蹤sql語句的話,會發現生成的sql會限制查詢範圍,這樣就能提高內存的使用率,當然這個功勞在EF,並不是GraphQL所要做的事,這也是ado.net和dapper類的ORM與GraphQL般配的原因。
相同的GrapQL,下圖是dapper查詢Product表的語句。
下圖是EF生成的語句,EF生成的語句更精確。