5

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

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。

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

4

2 回答 2

4

从“Scoped”切换到“Transient”将无法解决该问题,因为 Gql.Net 字段解析器是并行执行的。

根据您的示例,我希望您DbContext的构造函数注入到您的“数据库服务”类(userQueryServicegroupQueryService)中,并且这些构造函数注入到您的示例 GraphType 类中。因此,您的每个数据库服务都具有完全相同的范围副本DbContext

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

快速而简单的方法是使用“服务定位器”模式。

您将更改 db-services 以注入IServiceScopeFactory. 然后你在你的加载器方法(MyUserFuncMyGroupFunc)中使用它来创建一个范围,然后解析你的DbContext. 这种方法(“服务定位器”)的问题是对你的依赖DbContext隐藏在你的类中。

更好的方法(类似,但不是“服务定位器”)......

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

例子

所以假设你的userQueryService变量的类是这样的:

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>):

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使用自己的范围,因此它们不会像现在这样出现执行问题。

于 2020-03-11T22:59:10.057 回答
0

Graphql 是一个注册为单例的中间件。

因为 dbcontext 在 graphql 中使用,所以如果您使用“AddDbContext”(即“作用域”),它也不是真正的“作用域”,因为它位于“单例”主中间件中!

所以,上下文只会在第一次被实例化!

您可以通过2种方法解决:

1-将 graphql 注册为“范围”。但这意味着为每个请求加载所有 graphq 类型——>它很慢!

2-使用工厂模式来使用上下文和显式实例

这是使用 graphql .net 核心的“现实生活”示例

https://github.com/graphql-dotnet/graphql-dotnet/issues/576#issuecomment-626661695

编辑:这里是解析器级别的最新实现 https://github.com/fenomeno83/graphql-dotnet-globalization-demo

于 2020-05-11T11:51:46.963 回答