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. SOFA 源码分析 —— 服务引用过程

    前言 在前面的 SOFA 源码分析 -- 服务发布过程 文章中,我们分析了 SOFA 的服务发布过程,一个完整的 RPC 除了发布服务,当然还需要引用服务. So,今天就一起来看看 SOFA 是如何引 ...

  2. 微信小程序之获取用户位置权限(拒绝后提醒)

    微信小程序获取用户当前位置有三个方式: 1. wx.getLocation(多与wx.openLocation一起用) 获取当前的精度.纬度.速度.不需要授权.当type设置为gcj02 返回可用于w ...

  3. Qt中绘制五子棋棋盘

    一个需要做大作业的同学问我相关内容,就顺手写了一个,贴出来. 项目包含头文件 mainwindowh,源文件mainwindow.cpp和主函数main.cpp. 如下: mainwindow.h # ...

  4. 类设计:设计卖车的4S店

    class Car(object): # 定义车的方法 def move(self): print('---车在移动---') def stop(self): print('---停车---') # ...

  5. Django+xadmin打造在线教育平台(五)

    目录 在线教育平台(一)      在线教育平台(二) 在线教育平台(三)      在线教育平台(四) 在线教育平台(五)      在线教育平台(六) 在线教育平台(七)      在线教育平台( ...

  6. Hbuilder之开发Python

    .开发之前,安装Python 3.6 在Mac上安装Python 如果你正在使用Mac,系统是OS X 10.8~10.10,那么系统自带的Python版本是2.7.要安装最新的Python 3.6, ...

  7. mysql 和 oracle 的一些小知识

    有很多应用项目, 刚起步的时候用MYSQL数据库基本上能实现各种功能需求,随着应用用户的增多,数据量的增加,MYSQL渐渐地出现不堪重负的情况:连接很慢甚至宕机,于是就有把数据从MYSQL迁到ORAC ...

  8. PAT1129:Recommendation System

    1129. Recommendation System (25) 时间限制 400 ms 内存限制 65536 kB 代码长度限制 16000 B 判题程序 Standard 作者 CHEN, Yue ...

  9. Python基础之数据类型、变量、常量

    数据类型 整数:任意大小的整数,十六进制用0x前缀 浮点数:浮点数也就是小数,科学计数法1.23x109就是1.23e9,0.000012可以写成1.2e-5 字符串:以单引号'或双引号"括 ...

  10. 关于TCP/IP,必知必会的十个经典问题[转]

    关于TCP/IP,必知必会的十个问题 原创 2018-01-25 Ruheng 技术特工队   本文整理了一些TCP/IP协议簇中需要必知必会的十大问题,既是面试高频问题,又是程序员必备基础素养. 一 ...