由于DbContext不支持多线程的, 不能让DbContext被多个进程同时使用, 所有要实现并发的插入修改删除操作的关键在于创建多个DbContext, 确保每一个DbContext只执行一个任务
例子
-
.NET 框架自带依赖注入容器管理对象的生命周期, 我们可以直接使用
IServiceScopeFactory实例来获取多个DbContext实例[Route("api/[controller]")] public class OrdersController : ControllerBase { private readonly IServiceScopeFactory _scopeFactory; public OrdersController(IServiceScopeFactory scopeFactory) { _scopeFactory = scopeFactory; } } -
并发操作, 确保一个DbContext对应着一个任务
[HttpPost("parallel-safe")] public async Task<IActionResult> ParallelSafe([FromBody] List<Order> ordersToInsert) { // 分批并发插入,例如每批 10 个任务,每个任务独立 Scope var tasks = new List<Task>(); // 假设 ordersToInsert 包含 100 个订单,每个任务处理一部分 var batchSize = 10; for (int i = 0; i < ordersToInsert.Count; i += batchSize) { var batch = ordersToInsert.Skip(i).Take(batchSize).ToList(); tasks.Add(Task.Run(async () => { // ✅ 每个并发任务独立创建 Scope 和 Repository using (var scope = _scopeFactory.CreateScope()) { var repo = scope.ServiceProvider.GetRequiredService<Repository<Order>>(); foreach (var order in batch) { await repo.AddAsync(order); } await repo.SaveAsync(); // 每个任务在自己的 DbContext 上 SaveChanges } })); } await Task.WhenAll(tasks); return Ok($"Inserted {ordersToInsert.Count} orders in parallel."); }
进阶操作, 改为使用DbContextFactory来获取DbContext实例
设计优化, 为了实现并发操作就获取_scopeFactory来获取DbContext实例耦合度有点高, 推荐使用功能更加内聚, 职责更加专注于DbContext实例创建的DbContextFactory更加优雅
- 注册
DbContextFactory//使用AddDbContextFactory注册工厂,确保每次获取的DbContext实例都是独立的 builder.Services.AddDbContextFactory<Test2Context>(options => { options.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString)); });
注意事项:
-
DbContextFactory默认情况是单例服务, 也不推荐将其注册成其他服务 -
不推荐
DbContext和DbContextFactory, 同时注册, 会出现工厂在构建的时候获取一个生命周期为Scoped的DbContextOptions时 就会触发了 .NET Core 依赖注入(DI)的“作用域服务无法被单例服务消费”的限制。 -
如果一定要同时注册, 可以将
DbContextFactory注册成域生命周期, 但不推荐这种行为// 使用AddDbContextFactory注册工厂,确保每次获取的DbContext实例都是独立的 builder.Services.AddDbContextFactory<Test2Context>(options => { options.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString)); }, ServiceLifetime.Scoped); -
获取
DbContextFactory实例, 假设我们在Repository<T>类里操作public class Repository<TEntity> where TEntity : IEntity, new() { private readonly IDbContextFactory _dbContextFactory; public Repository(IDbContextFactory dbContextFactory) { _dbContextFactory = dbContextFactory; } } -
并发操作
public async Task<bool> ParallelSafe(List<Order> ordersToInsert) { // 分批并发插入,例如每批 10 个任务,每个任务独立 Scope var tasks = new List<Task>(); // 假设 ordersToInsert 包含 100 个订单,每个任务处理一部分 var batchSize = 10; for (int i = 0; i < ordersToInsert.Count; i += batchSize) { var batch = ordersToInsert.Skip(i).Take(batchSize).ToList(); tasks.Add(Task.Run(async () => { // ✅ 每个并发任务独立创建context using (var context = _dbContextFactory.CreateDbContext()) { await context.AddRangeAsync(batch); await repo.SaveAsync(); // 每个任务在自己的 DbContext 上 SaveChanges } })); } await Task.WhenAll(tasks); return true; }
进阶操作, 改为使用DbContextPool来实现DbContext池化操作
DbContext本身的创建和销毁行为本身并不直接影响系统与数据库的连接数, 对性能的损耗也很小, 但是在高性能场景, 这种损耗不可估量. 推荐使用DbContextPool来实现DbContext池化操作, 相较于前面的DbContextFactory来说, 这是一种成本更低, 性能更好的措施.
- 注册
DbContextPoolbuilder.Services.AddDbContextPool<Test2Context>(options => { options.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString)); });
注意事项:
-
跟
DbContextFactory一样,DbContextPool也是单例服务 -
跟
DbContextFactory一样, 注册了DbContextPool就不需要直接注册DbContext了, 其内部方法会注册的 -
DbContextFactory与DbContextPool相互不冲突, 可以同时注册他们并同时使用他们 -
跟
DbContextFactory不同的是, 注册了DbContextPool就完成了对DbContext池化操作, 只需要像是正常对DbContext编写并行操作即可 -
DbContextPool的poolsize默认大小为1024,要确保你使用数据库能接受的最大连接数的大小, 推荐poolsize的大小不超过你的数据库最大连接书的80%, 以及通过基准测试来确定最适合的poolsize的值builder.Services.AddDbContextPool<Test2Context>(options => { options.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString)); }, 64); // 设定poolsize为64 -
获取
IServiceScopeFactory实例来获取多个DbContext实例public class Repository<TEntity> where TEntity : IEntity, new() { private readonly IServiceScopeFactory _scopeFactory; public OrdersController(IServiceScopeFactory scopeFactory) { _scopeFactory = scopeFactory; } } -
并发操作
public async Task<bool> ParallelSafe(List<Order> ordersToInsert) { // 分批并发插入,例如每批 10 个任务,每个任务独立 Scope var tasks = new List<Task>(); // 假设 ordersToInsert 包含 100 个订单,每个任务处理一部分 var batchSize = 10; for (int i = 0; i < ordersToInsert.Count; i += batchSize) { var batch = ordersToInsert.Skip(i).Take(batchSize).ToList(); tasks.Add(Task.Run(async () => { // ✅ 每个并发任务独立创建 Scope 和 Repository using (var scope = _scopeFactory.CreateScope()) { var context = scope.ServiceProvider.GetRequiredService<>(); await context.AddRangeAsync(batch); await context.SaveAsync(); // 每个任务在自己的 DbContext 上 SaveChanges } })); } await Task.WhenAll(tasks); return true; } -
或者使用
DbContextFactory实例来获取多个DbContext实例public class Repository<TEntity> where TEntity : IEntity, new() { private readonly IDbContextFactory _dbContextFactory; public Repository(IDbContextFactory dbContextFactory) { _dbContextFactory = dbContextFactory; } } -
并发操作
public async Task<bool> ParallelSafe(List<Order> ordersToInsert) { // 分批并发插入,例如每批 10 个任务,每个任务独立 Scope var tasks = new List<Task>(); // 假设 ordersToInsert 包含 100 个订单,每个任务处理一部分 var batchSize = 10; for (int i = 0; i < ordersToInsert.Count; i += batchSize) { var batch = ordersToInsert.Skip(i).Take(batchSize).ToList(); tasks.Add(Task.Run(async () => { // ✅ 每个并发任务独立创建context using (var context = _dbContextFactory.CreateDbContext()) { await context.AddRangeAsync(batch); await repo.SaveAsync(); // 每个任务在自己的 DbContext 上 SaveChanges } })); } await Task.WhenAll(tasks); return true; }

最新评论