Entity Framework Core多线程使用时出现DbContext并发操作异常如何解决
问题根因
A second operation was started on this context before a previous operation completed
这个报错的本质是同一个DbContext实例被多个并行的异步操作同时访问,DbContext本身是非线程安全的,天生不支持并发操作。
你设置了DbContext为Transient依然报错的原因:
你注入的IOnTimeRequestsRepository是Scoped生命周期,在同一个请求范围内只会被实例化一次,Repository构造函数中注入的DbContext哪怕是Transient,也只会在Repository实例化时注入一次,因此你调用的两个Repository方法用的都是同一个DbContext实例,你用Task.WhenAll并行执行两个查询,自然会触发并发冲突。
解决方案
方案1:顺序执行查询(最简单成本最低)
不需要做任何架构调整,直接把并行执行的逻辑改成顺序await即可,分页的count和list查询性能损耗极小,顺序执行几乎不会影响接口性能:
public async Task<OnTimePaginationDto> getUserOnTimeRequests(int pageSize, int currentPage, long userId) { // 顺序await,避免同时操作同一个DbContext var count = await _onTimeRequestsRepository.getCountAsync(userId); var values = await _onTimeRequestsRepository.GetOnTimeRequestsAsync(pageSize, currentPage, userId); OnTimePaginationDto onTimePaginationDto = new OnTimePaginationDto { count = count, values = _mapper.Map<IList<ReadOnTimeRequestDto>>(values) }; return onTimePaginationDto; }
方案2:使用DbContextFactory创建独立DbContext(需要并行时可选)
如果你确实需要并行执行两个查询,可以使用EF Core官方提供的IDbContextFactory来为每个查询创建独立的DbContext实例:
- 调整服务注册逻辑,替换原来的
AddDbContext:
services.AddDbContextFactory<AppDbContext>(options => options.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking) .UseSqlServer(Configuration.GetConnectionString("DbConnection")));
- 在Repository中注入
IDbContextFactory<AppDbContext>,每个数据操作方法自行创建DbContext:
public class OnTimeRequestRepository : IOnTimeRequestsRepository { private readonly IDbContextFactory<AppDbContext> _dbContextFactory; // 注入Factory替代直接注入DbContext public OnTimeRequestRepository(IDbContextFactory<AppDbContext> dbContextFactory) { _dbContextFactory = dbContextFactory; } public async Task<int> getCountAsync(long userId) { // 每次查询创建独立的DbContext using var context = _dbContextFactory.CreateDbContext(); return await context.onTimeRequests .Where(i => (userId == 0 ? true : i.userId == userId)) .CountAsync(); } public async Task<List<OnTimeRequest>> GetOnTimeRequestsAsync(int pageSize, int currentPage, long userId) { using var context = _dbContextFactory.CreateDbContext(); return await context.onTimeRequests .Where(i => (userId == 0 ? true : i.userId == userId)) .OrderByDescending(i => i.id) .Skip((currentPage - 1) * pageSize) .Take(pageSize) .ToListAsync(); } }
调整后两个查询方法会使用各自独立的DbContext,此时再用Task.WhenAll并行执行也不会触发冲突。
内容的提问来源于stack exchange,提问作者arman
相关产品推荐
相关产品推荐

