zoukankan      html  css  js  c++  java
  • ASP.NET Core中使用GraphQL

    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 MVC 重点教程一周年版 第二回 UrlRouting
    ASP.NET MVC 重点教程一周年版 第三回 Controller与View
    DynamicData for Asp.net Mvc留言本实例 下篇 更新
    Asp.net MVC视频教程 18 单选与复选框
    使用ASP.NET MVC Futures 中的异步Action
    ASP.NET MVC RC 升级要注意的几点
    ATL、MFC、WTL CString 的今生前世
    msvcprt.lib(MSVCP90.dll) : error LNK2005:已经在libcpmtd.lib(xmutex.obj) 中定义
    关于Windows内存的一些参考文章
    Windows访问令牌相关使用方法
  • 原文地址:https://www.cnblogs.com/lwqlun/p/9972233.html
Copyright © 2011-2022 走看看