如何在数据加载器/GraphQL嵌套查询中处理并发DbContext访问? [英] How to handle concurrent DbContext access in dataloaders / GraphQL nested queries?

查看:191
本文介绍了如何在数据加载器/GraphQL嵌套查询中处理并发DbContext访问?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用几个使用注入的查询服务的数据加载器(这些查询服务又依赖于DbContext).看起来像这样:

I'm using a couple of dataloaders that use injected query services (which in turn have dependencies on a DbContext). It looks something like this:

Field<ListGraphType<UserType>>(
  "Users",
  resolve: context =>
  {
    var loader = accessor.Context.GetOrAddBatchLoader<Guid, IEnumerable<User>>(
      "MyUserLoader",
      userQueryService.MyUserFunc);

    return loader.LoadAsync(context.Source.UserId);
  });

Field<ListGraphType<GroupType>>(
  "Groups",
  resolve: context =>
  {
    var loader = accessor.Context.GetOrAddBatchLoader<Guid, IEnumerable<Group>>(
      "MyGroupLoader",
      groupQueryService.MyGroupFunc);

    return loader.LoadAsync(context.Source.GroupId);
  });

当我运行同时使用两个数据加载器的嵌套查询时,出现异常"A second operation started on this context before a previous asynchronous operation completed",因为两个数据加载器同时使用相同的DbContext.

When I run a nested query that concurrently uses both dataloaders I get an exception "A second operation started on this context before a previous asynchronous operation completed" because both dataloaders are using the same DbContext at the same time.

在查询中允许并发数据库访问而不必使用ServiceLifeTime.Transient仔细管理DbContext的最佳方法是什么?还是数据加载器可以公开知道何时处置瞬态DbContext的方法?

What's the best way to allow concurrent database access within the query without having to carefully manage DbContexts with ServiceLifeTime.Transient? Or can dataloader expose a way to know when to dispose of transient DbContexts?

推荐答案

从作用域"切换为瞬态"将无法解决问题,因为Gql.Net字段解析器是并行执行的.

The problem will not be solved with switching from "Scoped" to "Transient" because Gql.Net field resolvers execute in parallel.

根据您的示例,我希望您的DbContext被构造函数注入到您的"db service"类(userQueryServicegroupQueryService)中,而这些被构造函数注入到您的示例GraphType类中.因此,每个数据库服务都具有与DbContext完全相同的作用域副本.

Based on your example, I'm expecting that your DbContext is constructor-injected into your "db service" classes (userQueryService and groupQueryService), and those were constructor-injected into your example GraphType class. So each of your db services have the exact same, scoped copy of your DbContext.

解决方案是延迟解析您的DbContext.

The solution is to lazy-resolve your DbContext.

您将更改数据库服务以注入IServiceScopeFactory.然后,您可以在加载器方法(MyUserFuncMyGroupFunc)中使用它来创建作用域,然后解析您的DbContext.这种方法(服务定位器")的问题在于,对DbContext的依赖项隐藏在您的类中.

You'd change your db-services to inject an IServiceScopeFactory. You then use that in your loader methods (MyUserFunc and MyGroupFunc) to create a scope and then resolve your DbContext. The problem with this approach ("Service Locator") is that the dependency on your DbContext is hidden inside of your class.

在CodeReview.StackExchange 上使用此相对简单的代码代替IServiceScopeFactory<T>.您无需执行服务定位器"即可获得延迟解决方案;您的强类型依赖项在构造函数中声明.

Use this relatively simple bit of code here on CodeReview.StackExchange to instead use an IServiceScopeFactory<T>. You get the lazy-resolving without doing "Service Locator"; your strongly-typed dependencies are declared up in the constructor.

因此,假设您的userQueryService变量的类如下:

So pretend your userQueryService variable's class is like so:

MyDbContext _dbContext;
public UserQueryService(MyDbContext dbContext) => _dbContext = dbContext;

public async Task<IDictionary<Guid, IEnumerable<User>> MyUserFunc(IEnumerable<Guid> userIds)
{
    // code that uses _dbContext and returns the data...
}

将其更改为此(再次使用IServiceScopeFactory<T>):

Change it to this (again, using the IServiceScopeFactory<T>):

IServiceScopeFactory<MyDbContext> _dbFactory;
public UserQueryService(IServiceScopeFactory<MyDbContext> dbFactory) => _dbFactory = dbFactory;

public async Task<IDictionary<Guid, IEnumerable<User>> MyUserFunc(IEnumerable<Guid> userIds)
{
    using var scope = _dbFactory.CreateScope();
    var dbContext = scope.GetRequiredService();
    // code that uses dbContext and returns the data...
}

现在,当Gql.Net的解析器(在这种情况下为数据加载器)最终执行此方法时,每次使用DbContext时都使用自己的作用域,因此它们不会像现在那样出现执行问题

Now, when Gql.Net's resolvers (well, in this case, the data loader) ultimately execute this method, each use of your DbContext use their own scope and so they won't have execution problems like you have now.

这篇关于如何在数据加载器/GraphQL嵌套查询中处理并发DbContext访问?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆