ASP.NET Core中使用GraphQL - 目录


在之前的几章中,我们的GraphQL查询是没有优化过的。下面我们以CustomerType中的orders查询为例

CustomerType.cs
Field<ListGraphType<OrderType>, IEnumerable<Order>>()
.Name("Orders")
.ResolveAsync(ctx =>
{
return dataStore.GetOrdersAsync();
});

在这个查询中,我们获取了某个顾客中所有的订单, 这里如果你只是获取一些标量字段,那很简单。

但是如果需要获取一些关联属性呢?例如查询系统中的所有订单,在订单信息中附带顾客信息。

OrderType
public OrderType(IDataStore dataStore, IDataLoaderContextAccessor accessor)
{
Field(o => o.Tag);
Field(o => o.CreatedAt);
Field<CustomerType, Customer>()
.Name("Customer")
.ResolveAsync(ctx =>
{
return dataStore.GetCustomerByIdAsync(ctx.Source.CustomerId);
});
}

这里当获取customer信息的时候,系统会另外初始化一个请求,以便从数据仓储中查询订单相关的顾客信息。

如果你了解dotnet cli, 你可以针对以下查询,在控制台输出所有的EF查询日志

{
orders{
tag
createdAt
customer{
name
billingAddress
}
}
}

查询结果:

{
"data": {
"orders": [
{
"tag": "XPS 13",
"createdAt": "2018-11-11",
"customer": {
"name": "Lamond Lu",
"billingAddress": "Test Address"
}
},
{
"tag": "XPS 15",
"createdAt": "2018-11-11",
"customer": {
"name": "Lamond Lu",
"billingAddress": "Test Address"
}
}
]
}
}

产生日志如下:

info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (16ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
SELECT [o].[OrderId], [o].[CreatedAt], [o].[CustomerId], [o].[CustomerId1], [o].[Tag]
FROM [Orders] AS [o]
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (6ms) [Parameters=[@__get_Item_0='?' (DbType = Int32)], CommandType='Text', CommandTimeout='30'] SELECT TOP(1) [e].[CustomerId], [e].[BillingAddress], [e].[Name]
FROM [Customers] AS [e]
WHERE [e].[CustomerId] = @__get_Item_0
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (5ms) [Parameters=[@__get_Item_0='?' (DbType = Int32)], CommandType='Text', CommandTimeout='30'] SELECT TOP(1) [e].[CustomerId], [e].[BillingAddress], [e].[Name]
FROM [Customers] AS [e]
WHERE [e].[CustomerId] = @__get_Item_0
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
Request finished in 864.2749ms 200

从日志上我们很清楚的看到,这个查询使用了3个查询语句,第一个语句查询所有的订单信息,第二个和第三个请求分别查询了2个订单的顾客信息。这里可以想象如果这里有N的订单,就会产生N+1个查询语句,这是非常不效率的。正常情况下我们其实可以通过2条语句就完成上述的查询,后面查询单个顾客信息其实可以整合成一条语句。

为了实现这个效果,我们就需要介绍一下GraphQL中的DataLoader

DataLoaderGraphQL中的一个重要功能,它为GraphtQL查询提供了批处理和缓存的功能。

为了使用DataLoader, 我们首先需要在Startup.cs中注册2个新服务IDataLoaderContextAccessorDataLoaderDocumentListener

Startup.cs
services.AddSingleton<IDataLoaderContextAccessor, DataLoaderContextAccessor>();
services.AddSingleton<DataLoaderDocumentListener>();

如果你的某个GraphQL类型需要DataLoader, 你就可以在其构造函数中注入一个IDataLoaderContextAccessor接口对象。

但是为了使用DataLoader, 我们还需要将它添加到我们的中间件中。

GraphQLMiddleware.cs
public async Task InvokeAsync(HttpContext httpContext, ISchema schema, IServiceProvider serviceProvider)
{
....
.... var result = await _executor.ExecuteAsync(doc =>
{
....
....
doc.Listeners.Add(serviceProvider .GetRequiredService<DataLoaderDocumentListener>());
}).ConfigureAwait(false); ....
....
}

下一步,我们需要为我们的仓储类,添加一个新方法,这个方法可以根据顾客的id列表,返回所有的顾客信息。

DataStore.cs
public async Task<Dictionary<int, Customer>> GetCustomersByIdAsync(
IEnumerable<int> customerIds,
CancellationToken token)
{
return await _context.Customers
.Where(i => customerIds.Contains(i.CustomerId))
.ToDictionaryAsync(x => x.CustomerId);
}

然后我们修改OrderType

OrderType
Field<CustomerType, Customer>()
.Name("Customer")
.ResolveAsync(ctx =>
{
var customersLoader = accessor.Context.GetOrAddBatchLoader<int, Customer>("GetCustomersById", dataStore.GetCustomersByIdAsync);
return customersLoader.LoadAsync(ctx.Source.CustomerId);
});

完成以上修改之后,我们重新运行项目, 使用相同的query, 结果如下,查询语句的数量变成了2个,效率大大提高

info: Microsoft.EntityFrameworkCore.Infrastructure[10403]
Entity Framework Core 2.1.4-rtm-31024 initialized 'ApplicationDbContext' using provider 'Microsoft.EntityFrameworkCore.SqlServer' with options: None
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (19ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
SELECT [o].[OrderId], [o].[CreatedAt], [o].[CustomerId], [o].[CustomerId1], [o].[Tag]
FROM [Orders] AS [o]
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (10ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
SELECT [i].[CustomerId], [i].[BillingAddress], [i].[Name]
FROM [Customers] AS [i]
WHERE [i].[CustomerId] IN (1)

DataLoader背后的原理

GetOrAddBatchLoader方法会等到所有查询的顾客id列表准备好之后才会执行,它会一次性把所有查询id的顾客信息都收集起来。 这种技术就叫做批处理,使用了这种技术之后,无论有多少个关联的顾客信息,系统都只会发出一次请求来获取所有数据。

本文源代码: https://github.com/lamondlu/GraphQL_Blogs/tree/master/Part%20X

ASP.NET Core中使用GraphQL - 最终章 Data Loader的更多相关文章

  1. ASP.NET Core中使用GraphQL - 第一章 Hello World

    前言 你是否已经厌倦了REST风格的API? 让我们来聊一下GraphQL. GraphQL提供了一种声明式的方式从服务器拉取数据.你可以从GraphQL官网中了解到GraphQL的所有优点.在这一系 ...

  2. ASP.NET Core中使用GraphQL - 第五章 字段, 参数, 变量

    ASP.NET Core中使用GraphQL ASP.NET Core中使用GraphQL - 第一章 Hello World ASP.NET Core中使用GraphQL - 第二章 中间件 ASP ...

  3. ASP.NET Core中使用GraphQL - 第二章 中间件

    前文:ASP.NET Core中使用GraphQL - 第一章 Hello World 中间件 如果你熟悉ASP.NET Core的中间件,你可能会注意到之前的博客中我们已经使用了一个中间件, app ...

  4. ASP.NET Core中使用GraphQL - 第三章 依赖注入

    ASP.NET Core中使用GraphQL ASP.NET Core中使用GraphQL - 第一章 Hello World ASP.NET Core中使用GraphQL - 第二章 中间件 SOL ...

  5. ASP.NET Core中使用GraphQL - 第四章 GraphiQL

    ASP.NET Core中使用GraphQL ASP.NET Core中使用GraphQL - 第一章 Hello World ASP.NET Core中使用GraphQL - 第二章 中间件 ASP ...

  6. ASP.NET Core中使用GraphQL - 第六章 使用EF Core作为持久化仓储

    ASP.NET Core中使用GraphQL ASP.NET Core中使用GraphQL - 第一章 Hello World ASP.NET Core中使用GraphQL - 第二章 中间件 ASP ...

  7. ASP.NET Core中使用GraphQL - 第七章 Mutation

    ASP.NET Core中使用GraphQL - 目录 ASP.NET Core中使用GraphQL - 第一章 Hello World ASP.NET Core中使用GraphQL - 第二章 中间 ...

  8. ASP.NET Core中使用GraphQL - 第八章 在GraphQL中处理一对多关系

    ASP.NET Core中使用GraphQL - 目录 ASP.NET Core中使用GraphQL - 第一章 Hello World ASP.NET Core中使用GraphQL - 第二章 中间 ...

  9. ASP.NET Core中使用GraphQL - 第九章 在GraphQL中处理多对多关系

    ASP.NET Core中使用GraphQL ASP.NET Core中使用GraphQL - 第一章 Hello World ASP.NET Core中使用GraphQL - 第二章 中间件 ASP ...

随机推荐

  1. ScalaPB(2): 在scala中用gRPC实现微服务

    gRPC是google开源提供的一个RPC软件框架,它的特点是极大简化了传统RPC的开发流程和代码量,使用户可以免除许多陷阱并聚焦于实际应用逻辑中.作为一种google的最新RPC解决方案,gRPC具 ...

  2. SOFA 源码分析 —— 过滤器设计

    前言 通常 Web 服务器在处理请求时,都会使用过滤器模式,无论是 Tomcat ,还是 Netty,过滤器的好处是能够将处理的流程进行分离和解耦,比如一个 Http 请求进入服务器,可能需要解析 h ...

  3. Windows10 Ubuntu bash 配置 LAMP+JDK+Tomcat

    Windows10的Bash省去了装ubuntu虚拟机的麻烦,而且可以直接用Windows下的浏览器去访问了,Exciting有没有,再也不用去忍受日常崩溃的chromium和慢慢的firefox了. ...

  4. thinkphp实现文件上传

    文件上传详细讲解 http://www.thinkphp.cn/info/194.html 上传根目录不存在问题解决方法 http://www.thinkphp.cn/topic/10779.html

  5. PAT1093: Count PAT's

    1093. Count PAT's (25) 时间限制 120 ms 内存限制 65536 kB 代码长度限制 16000 B 判题程序 Standard 作者 CAO, Peng The strin ...

  6. cmd登录系统用户

    1.  sqlplus /nolog 2.  conn system/密码 as sysdba  或conn / as sysdba  或conn sys/密码 as sysdba 注:system系 ...

  7. explicit的作用

    用来修饰类的构造函数,被修饰的构造函数的类,不能发生相应的隐式类型转换,只能以显示的方式进行类型转换,例如:不加:Circle A = Circle(1.23) 加上之后:只能写:Circle A(1 ...

  8. 注解@EnableDiscoveryClient,@EnableEurekaClient的区别

    SpringCLoud中的"Discovery Service"有多种实现,比如:eureka, consul, zookeeper. 1,@EnableDiscoveryClie ...

  9. Java Web之九九乘法表

    NineTabs.jsp 1 <%@ page language="java" import="java.util.*" contentType=&quo ...

  10. asp.net mvc 使用 Autocomplete 实现类似百度,谷歌动态搜索条提示框。

    Autocomplete是一个Jquery的控件,用法比较简单. 大家先看下效果: 当文本框中输入内容,自动检索数据库给出下拉框进行提示功能. 需要用此控件大家先到它的官方网站进行下载最新版本: ht ...