如何使用存储库模式在 EF Core 3.0 中进行并行异步查询?
How to make parallel async queries in EF Core 3.0 with repository pattern?
我有像
这样的存储库
public interface IEmployeeRepository
{
Task<EmployeeSettings> GetEmployeeSettings(int employeeId);
Task<ICollection<DepartmentWorkPosition>> GetWorkPositions(int employeeId);
}
存储库构造函数(DbContext 注入):
public EmployeeRepository(EmployeeDbContext dbContext)
{
_dbContext = dbContext;
}
并在 EF Core 2.0 中像这样调用它
var settingsTask = _employeeRepository
.GetEmployeeSettings(employeeId.Value);
var workPositionsTask = _employeeRepository
.GetWorkPositions(employeeId.Value);
await Task.WhenAll(settingsTask, workPositionsTask);
// do other things...
问题:
对于 EF Core 3.0,存在 InvalidOperationException:在上一个操作完成之前在此上下文中启动了第二个操作...
DbContext 在 ConfigureServices 中注册,如
services.AddDbContext<EmployeeDbContext>(ServiceLifetime.Transient);
教程如下:
Entity Framework Core 不支持在同一个 DbContext 实例上 运行 多个并行操作。
但是!如何在异步存储库中使用它?
How to use it with repositories in async?
每个存储库只能有一个同步异步请求。如果一次需要多个,则需要多个存储库。这可能需要您将存储库 factory 注入到您的类型中。
随便写:
var settings = await _employeeRepository.GetEmployeeSettings(employeeId.Value);
var workPositions = await _employeeRepository.GetWorkPositions(employeeId.Value);
是的。 EF Core 不支持在同一上下文实例上 运行 的多个并行操作。在开始下一个操作之前,您应该始终等待操作完成。这通常是通过在每个异步操作上使用 await 关键字来完成的。
看看https://docs.microsoft.com/en-us/ef/core/querying/async
使用工厂并显式实例化上下文。
Startup.cs
//classical dbcontext registration
services.AddDbContext<TestDB>(
options => options.UseSqlServer(
Configuration.GetConnectionString("Test")));
//factory
//in case we want parallellize more queries at the same request, we can't use the same connection. So, because dbcontext is instantiate at request time this would generate exception, so we need to use factory and explicit "using" to explicitly manage dbcontext lifetime
var optionsBuilder = new DbContextOptionsBuilder<TestDB>();
optionsBuilder.UseSqlServer(Configuration.GetConnectionString("Test"));
services.AddSingleton(s => new Func<TestDB>(() => new TestDB(optionsBuilder.Options)));
服务class
public class TestService
{
private readonly TestDB _testDb;
private readonly Func<TestDB> _testDbfunct;
public TestService(TestDB testDb, Func<TestDB> testDbfunct)
{
_testDb = testDb;
_testDbfunct = testDbfunct;
}
//mixed classical request dbcontext and factory approaches
public async Task<string> TestMultiple(int id, bool newConnection = false) //we need to add optional newConnection parameter and the end of other parameters
{
//use request connection (_testDb) if newconnection is false, otherwise instantiate a new connection using factory. null inside "using" means that "using" is not used
//use newconnection = true if you want run parallel queries, so you need different connection for each one
TestDB testDb = _testDb;
using (newConnection ? testDb = _testDbfunct() : null)
{
return await (from t in testDb.Table where t.id == id select t.code).FirstOrDefaultAsync();
}
}
}
测试class
//instantiate dbcontext for each call, so we can parallellize
[TestMethod]
public async Task TestMultiple()
{
//test1 and test2 starts in parallel without test2 that need to wait the end of test1. For each one a Task in returned
var test1 = _testService.TestMultiple(1,true);
var test2 = _testService.TestMultiple(2,true);
//wait test1 and test2 return
string code1 = await test1;
string code2 = await test2;
}
//use request dbcontext
[TestMethod]
public async Task TestClassic()
{
string code = await _testService.TestMultiple(3);
}
注意:在新的 .net core 5 中,您可以使用 buildin AddDbContextFactory 而不是像我的示例中那样创建自定义工厂
我有像
这样的存储库public interface IEmployeeRepository
{
Task<EmployeeSettings> GetEmployeeSettings(int employeeId);
Task<ICollection<DepartmentWorkPosition>> GetWorkPositions(int employeeId);
}
存储库构造函数(DbContext 注入):
public EmployeeRepository(EmployeeDbContext dbContext)
{
_dbContext = dbContext;
}
并在 EF Core 2.0 中像这样调用它
var settingsTask = _employeeRepository
.GetEmployeeSettings(employeeId.Value);
var workPositionsTask = _employeeRepository
.GetWorkPositions(employeeId.Value);
await Task.WhenAll(settingsTask, workPositionsTask);
// do other things...
问题:
对于 EF Core 3.0,存在 InvalidOperationException:在上一个操作完成之前在此上下文中启动了第二个操作...
DbContext 在 ConfigureServices 中注册,如
services.AddDbContext<EmployeeDbContext>(ServiceLifetime.Transient);
教程如下: Entity Framework Core 不支持在同一个 DbContext 实例上 运行 多个并行操作。
但是!如何在异步存储库中使用它?
How to use it with repositories in async?
每个存储库只能有一个同步异步请求。如果一次需要多个,则需要多个存储库。这可能需要您将存储库 factory 注入到您的类型中。
随便写:
var settings = await _employeeRepository.GetEmployeeSettings(employeeId.Value);
var workPositions = await _employeeRepository.GetWorkPositions(employeeId.Value);
是的。 EF Core 不支持在同一上下文实例上 运行 的多个并行操作。在开始下一个操作之前,您应该始终等待操作完成。这通常是通过在每个异步操作上使用 await 关键字来完成的。 看看https://docs.microsoft.com/en-us/ef/core/querying/async
使用工厂并显式实例化上下文。
Startup.cs
//classical dbcontext registration
services.AddDbContext<TestDB>(
options => options.UseSqlServer(
Configuration.GetConnectionString("Test")));
//factory
//in case we want parallellize more queries at the same request, we can't use the same connection. So, because dbcontext is instantiate at request time this would generate exception, so we need to use factory and explicit "using" to explicitly manage dbcontext lifetime
var optionsBuilder = new DbContextOptionsBuilder<TestDB>();
optionsBuilder.UseSqlServer(Configuration.GetConnectionString("Test"));
services.AddSingleton(s => new Func<TestDB>(() => new TestDB(optionsBuilder.Options)));
服务class
public class TestService
{
private readonly TestDB _testDb;
private readonly Func<TestDB> _testDbfunct;
public TestService(TestDB testDb, Func<TestDB> testDbfunct)
{
_testDb = testDb;
_testDbfunct = testDbfunct;
}
//mixed classical request dbcontext and factory approaches
public async Task<string> TestMultiple(int id, bool newConnection = false) //we need to add optional newConnection parameter and the end of other parameters
{
//use request connection (_testDb) if newconnection is false, otherwise instantiate a new connection using factory. null inside "using" means that "using" is not used
//use newconnection = true if you want run parallel queries, so you need different connection for each one
TestDB testDb = _testDb;
using (newConnection ? testDb = _testDbfunct() : null)
{
return await (from t in testDb.Table where t.id == id select t.code).FirstOrDefaultAsync();
}
}
}
测试class
//instantiate dbcontext for each call, so we can parallellize
[TestMethod]
public async Task TestMultiple()
{
//test1 and test2 starts in parallel without test2 that need to wait the end of test1. For each one a Task in returned
var test1 = _testService.TestMultiple(1,true);
var test2 = _testService.TestMultiple(2,true);
//wait test1 and test2 return
string code1 = await test1;
string code2 = await test2;
}
//use request dbcontext
[TestMethod]
public async Task TestClassic()
{
string code = await _testService.TestMultiple(3);
}
注意:在新的 .net core 5 中,您可以使用 buildin AddDbContextFactory 而不是像我的示例中那样创建自定义工厂