ASP.NET Core中使用GraphQL - 目錄html
在以前的幾章中,咱們的<code>GraphQL</code>查詢是沒有優化過的。下面咱們以<code>CustomerType</code>中的<code>orders</code>查詢爲例git
Field<ListGraphType<OrderType>, IEnumerable<Order>>() .Name("Orders") .ResolveAsync(ctx => { return dataStore.GetOrdersAsync(); });
在這個查詢中,咱們獲取了某個顧客中全部的訂單, 這裏若是你只是獲取一些標量字段,那很簡單。github
可是若是須要獲取一些關聯屬性呢?例如查詢系統中的全部訂單,在訂單信息中附帶顧客信息。json
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); }); }
這裏當獲取<code>customer</code>信息的時候,系統會另外初始化一個請求,以便從數據倉儲中查詢訂單相關的顧客信息。c#
若是你瞭解<code>dotnet cli</code>, 你能夠針對如下查詢,在控制檯輸出全部的EF查詢日誌緩存
{ orders{ tag createdAt customer{ name billingAddress } } }
查詢結果:async
{ "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" } } ] } }
產生日誌以下:ide
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條語句就完成上述的查詢,後面查詢單個顧客信息其實能夠整合成一條語句。函數
爲了實現這個效果,咱們就須要介紹一下<code>GraphQL</code>中的<code>DataLoader</code>。優化
<code>DataLoader</code>是<code>GraphQL</code>中的一個重要功能,它爲<code>GraphtQL</code>查詢提供了批處理和緩存的功能。
爲了使用<code>DataLoader</code>, 咱們首先須要在<code>Startup.cs</code>中註冊2個新服務IDataLoaderContextAccessor
和DataLoaderDocumentListener
services.AddSingleton<IDataLoaderContextAccessor, DataLoaderContextAccessor>(); services.AddSingleton<DataLoaderDocumentListener>();
若是你的某個<code>GraphQL</code>類型須要<code>DataLoader</code>, 你就能夠在其構造函數中注入一個<code>IDataLoaderContextAccessor</code>接口對象。
可是爲了使用<code>DataLoader</code>, 咱們還須要將它添加到咱們的中間件中。
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列表,返回全部的顧客信息。
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); }
而後咱們修改<code>OrderType</code>類
Field<CustomerType, Customer>() .Name("Customer") .ResolveAsync(ctx => { var customersLoader = accessor.Context.GetOrAddBatchLoader<int, Customer>("GetCustomersById", dataStore.GetCustomersByIdAsync); return customersLoader.LoadAsync(ctx.Source.CustomerId); });
完成以上修改以後,咱們從新運行項目, 使用相同的<code>query</code>, 結果以下,查詢語句的數量變成了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)
<code>DataLoader</code>背後的原理
<code>GetOrAddBatchLoader</code>方法會等到全部查詢的顧客id列表準備好以後纔會執行,它會一次性把全部查詢id的顧客信息都收集起來。 這種技術就叫作批處理,使用了這種技術以後,不管有多少個關聯的顧客信息,系統都只會發出一次請求來獲取全部數據。
本文源代碼: https://github.com/lamondlu/GraphQL_Blogs/tree/master/Part%20X