ASP.NET Core中使用GraphQL - 最终章 Data Loader

ASP.NET Core中使用GraphQL - 目录
- ASP.NET Core中使用GraphQL - 第一章 Hello World
- ASP.NET Core中使用GraphQL - 第二章 中间件
- ASP.NET Core中使用GraphQL - 第三章 依赖注入
- ASP.NET Core中使用GraphQL - 第四章 GrahpiQL
- ASP.NET Core中使用GraphQL - 第五章 字段, 参数, 变量
- ASP.NET Core中使用GraphQL - 第六章 使用EF Core作为持久化仓储
- ASP.NET Core中使用GraphQL - 第七章 Mutation
- ASP.NET Core中使用GraphQL - 第八章 在GraphQL中处理一对多关系
- ASP.NET Core中使用GraphQL - 第九章 在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。
DataLoader是GraphQL中的一个重要功能,它为GraphtQL查询提供了批处理和缓存的功能。
为了使用DataLoader, 我们首先需要在Startup.cs中注册2个新服务IDataLoaderContextAccessor 和DataLoaderDocumentListener
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的更多相关文章
- ASP.NET Core中使用GraphQL - 第一章 Hello World
前言 你是否已经厌倦了REST风格的API? 让我们来聊一下GraphQL. GraphQL提供了一种声明式的方式从服务器拉取数据.你可以从GraphQL官网中了解到GraphQL的所有优点.在这一系 ...
- ASP.NET Core中使用GraphQL - 第五章 字段, 参数, 变量
ASP.NET Core中使用GraphQL ASP.NET Core中使用GraphQL - 第一章 Hello World ASP.NET Core中使用GraphQL - 第二章 中间件 ASP ...
- ASP.NET Core中使用GraphQL - 第二章 中间件
前文:ASP.NET Core中使用GraphQL - 第一章 Hello World 中间件 如果你熟悉ASP.NET Core的中间件,你可能会注意到之前的博客中我们已经使用了一个中间件, app ...
- ASP.NET Core中使用GraphQL - 第三章 依赖注入
ASP.NET Core中使用GraphQL ASP.NET Core中使用GraphQL - 第一章 Hello World ASP.NET Core中使用GraphQL - 第二章 中间件 SOL ...
- ASP.NET Core中使用GraphQL - 第四章 GraphiQL
ASP.NET Core中使用GraphQL ASP.NET Core中使用GraphQL - 第一章 Hello World ASP.NET Core中使用GraphQL - 第二章 中间件 ASP ...
- ASP.NET Core中使用GraphQL - 第六章 使用EF Core作为持久化仓储
ASP.NET Core中使用GraphQL ASP.NET Core中使用GraphQL - 第一章 Hello World ASP.NET Core中使用GraphQL - 第二章 中间件 ASP ...
- ASP.NET Core中使用GraphQL - 第七章 Mutation
ASP.NET Core中使用GraphQL - 目录 ASP.NET Core中使用GraphQL - 第一章 Hello World ASP.NET Core中使用GraphQL - 第二章 中间 ...
- ASP.NET Core中使用GraphQL - 第八章 在GraphQL中处理一对多关系
ASP.NET Core中使用GraphQL - 目录 ASP.NET Core中使用GraphQL - 第一章 Hello World ASP.NET Core中使用GraphQL - 第二章 中间 ...
- ASP.NET Core中使用GraphQL - 第九章 在GraphQL中处理多对多关系
ASP.NET Core中使用GraphQL ASP.NET Core中使用GraphQL - 第一章 Hello World ASP.NET Core中使用GraphQL - 第二章 中间件 ASP ...
随机推荐
- JSF-受管Bean与EL表达式
受管Bean与EL表达式 1)编写Bean:①有一个不带形参的构造方法 ②getXxx.setXxx ③一般要实现io.Serializable接口 2)声明受管Bean:①bean名称为外界访问其属 ...
- 关于Elasticsearch 使用 MatchPhrase搜索的一些坑
对分词字段检索使用的通常是match查询,对于短语查询使用的是matchphrase查询,但是并不是matchphrase可以直接对分词字段进行不分词检索(也就是业务经常说的精确匹配),下面有个例子, ...
- JavaScript中对数组和数组API的认识
JavaScript中对数组和数组API的认识 一.数组概念: 数组是JavaScript中的一类特殊的对象,用一对中括号“[]”表示,用来在单个的变量中存储多个值.在数组中,每个值都有一个对应的不重 ...
- Java中浮点数的精度问题 【转】
当您在计算Money的时候,请看好了!!!要不损失了别后悔!!! 现象1: public static void main(String[] args) { System.out.println(0. ...
- python实现四则运算和效能分析
代码github地址:https://github.com/yiduobaozhi/-1 PSP表格: 预测时间(分钟) planning 计划 15 Estimate 估计这个任务需要多少时间 10 ...
- SQL之left join,inner join,right join
left join(左联接) 返回包括左表中的所有记录和右表中联结字段相等的记录 right join(右联接) 返回包括右表中的所有记录和左表中联结字段相等的记录inner join(等值连接) 只 ...
- 使用input file上传文件中onChange事件只触发一次问题
每次上传文件的时候,都会将当前的文件路径保存至$event.target.value中,当第二次选择文件时,由于两次$event.target.value相同,所以不会触发change事件. 解决方案 ...
- 【转】mysql索引使用技巧及注意事项
一.索引的作用 一般的应用系统,读写比例在10:1左右,而且插入操作和一般的更新操作很少出现性能问题,遇到最多的,也是最容易出问题的,还是一些复杂的查询操作,所以查询语句的优化显然是重中之重. 在数据 ...
- MySql 物理文件组成
1.日志文件 MySQL 各类日志文件介绍 2.数据文件 MySQL 各类数据文件介绍
- @ConditionalOnMissingBean注解理解
@ConditionalOnMissingBean注解作用在@bean定义上,它的作用就是在容器加载它作用的bean时,检查容器中是否存在目标类型(ConditionalOnMissingBean注解 ...