我有一个.NET Core 1.1 API,其中包含EF Core 1.1,并使用Microsoft默认的依赖注入来为我的服务提供DbContext。(参考:https://learn.microsoft.com/en-us/aspnet/core/data/ef-mvc/intro#register-the-context-with-dependency-injection)
现在,我正在研究并行化数据库读取作为一种优化方法,使用WhenAll
所以不是:
var result1 = await _dbContext.TableModel1.FirstOrDefaultAsync(x => x.SomeId == AnId);
var result2 = await _dbContext.TableModel2.FirstOrDefaultAsync(x => x.SomeOtherProp == AProp);
我使用:
var repositoryTask1 = _dbContext.TableModel1.FirstOrDefaultAsync(x => x.SomeId == AnId);
var repositoryTask2 = _dbContext.TableModel2.FirstOrDefaultAsync(x => x.SomeOtherProp == AProp);
(var result1, var result2) = await (repositoryTask1, repositoryTask2 ).WhenAll();
这都很好,但当我在这些数据库仓库访问类之外使用同样的策略,并在我的控制器中通过WhenAll调用这些相同的方法以跨多个服务时,情况就不同了:
var serviceTask1 = _service1.GetSomethingsFromDb(Id);
var serviceTask2 = _service2.GetSomeMoreThingsFromDb(Id);
(var dataForController1, var dataForController2) = await (serviceTask1, serviceTask2).WhenAll();
现在,当我从我的控制器调用它时,我随机地会遇到并发错误,例如:
System.InvalidOperationException: ExecuteReader requires an open and available Connection. The connection's current state is closed.
我认为原因是因为有时这些线程尝试同时访问相同的表。我知道这是 EF Core 的设计,如果我想的话,可以每次创建一个新的 dbContext,但我正在尝试看看是否有解决方法。这时我发现了 Mehdi El Gueddari 写的这篇好文章:http://mehdi.me/ambient-dbcontext-in-ef6/
在这篇文章中,他承认了这个限制:
注入的 DbContext 防止您能够在服务中引入多线程或任何形式的并行执行流。
并提供了使用 DbContextScope
的自定义解决方案。
但是,即使使用 DbContextScope,他也提出了一个警告,即它不能在并行情况下工作(我上面试图做的):
如果您尝试在 DbContextScope 的上下文中启动多个并行任务(例如通过创建多个线程或多个 TPL 任务),您将遇到大麻烦。这是因为环境 DbContextScope 将流经所有使用您的并行任务的线程。
他在这里的最后一点引导我提出了我的问题:
一般来说,在单个业务事务中并行访问数据库几乎没有任何好处,只会增加显着的复杂性。在业务事务的上下文中执行的任何并行操作都不应访问数据库。
在我的控制器中,我是否不应该在这种情况下使用 WhenAll,而应该一个接一个地使用 await?或者依赖注入 DbContext 在这里是更根本的问题,因此应该通过某种工厂每次创建/提供一个新的 DbContext?
Repository1
是什么?如果你正在使用EF,你不需要使用存储库(反)模式,因为这就是你的dbContext
。你可以有一个具有生成查询方法的类,但那不是一个存储库,它只是一个提供查询的包装器。 - Daiusing(){}
块内直接返回一个Task<T>
- 相反应该在using(){}
块内使用await
- 这不会影响你的代码并行化能力。 - Dai