EF Core上下文多异步调用报错:Blazor Server库存查询问题排查
问题分析
出现a second operation was started on this context instance before a previous operation completed错误的核心原因:
- Blazor Server中,Scoped生命周期的DbContext会绑定到用户的SignalR会话,同一用户短时间内触发的多次搜索请求会共享同一个DbContext实例,但EF Core的DbContext本身不是线程安全的,不允许同时执行多个操作。
- 仓储代码中使用了同步的
Count()方法,而非异步的CountAsync(),同步操作会阻塞上下文,进一步加剧并发冲突的概率。
解决方案
1. 替换同步EF操作为异步方法
将仓储中的同步计数改为异步版本,确保整个查询流程都是非阻塞的异步操作:
// 原代码 int count = defaultQuery.Count(); // 修改为异步版本 int count = await defaultQuery.CountAsync();
2. 实现输入防抖,减少并发请求
在Razor组件中添加防抖逻辑,等待用户停止输入一段时间后再执行搜索,避免短时间内多次触发请求:
private CancellationTokenSource? _debounceCts; private async Task SearchUserInventoryProducts(int pageNumber) { // 取消前一次未执行的防抖请求 _debounceCts?.Cancel(); _debounceCts = new CancellationTokenSource(); try { // 等待300ms,用户停止输入后再执行搜索 await Task.Delay(300, _debounceCts.Token); PaginationFilter.PageNumber = pageNumber; UserInventoryProductMappings = await _userInventoryProductMappingService .SearchByUserInventoryId(PaginationFilter, UserInventory.UserInventoryId); } catch (OperationCanceledException) { // 防抖请求被取消,直接忽略 } }
3. 使用DbContextFactory创建独立上下文实例
如果上述方法仍无法解决问题,可以改用IDbContextFactory为每个仓储操作创建独立的DbContext实例,彻底避免共享上下文的并发冲突:
步骤1:修改DbContext注册方式
// 原代码 builder.Services.AddDbContext<NimaDbContext>(options => { options.UseSqlServer(builder.Configuration.GetConnectionString("NimaDbConnection")); }); // 修改为使用DbContextFactory builder.Services.AddDbContextFactory<NimaDbContext>(options => { options.UseSqlServer(builder.Configuration.GetConnectionString("NimaDbConnection")); });
步骤2:改造仓储层,使用工厂创建上下文
private readonly IDbContextFactory<NimaDbContext> _contextFactory; private readonly ILogger<UserInventoryProductMappingRepository> _logger; // 构造函数注入IDbContextFactory public UserInventoryProductMappingRepository(IDbContextFactory<NimaDbContext> contextFactory, ILogger<UserInventoryProductMappingRepository> logger) { _contextFactory = contextFactory; _logger = logger; } public async Task<PagedResponse<UserInventoryProductMapping>> SearchByUserInventoryId(PaginationQuery query, int userInventoryId) { try { // 每次操作创建新的DbContext实例 using var context = _contextFactory.CreateDbContext(); var defaultQuery = context.Set<UserInventoryProductMapping>().Where(x => x.UserInventoryId == userInventoryId); // 后续查询逻辑保持不变,确保所有操作都用await if (query.SearchString != null) { defaultQuery = defaultQuery.Where(x => x.Product.NameLabel.LabelDescriptions.Any(x => x.Description.Contains(query.SearchString))); } if (query.SortBy != null && query.SortById != null) { switch (query.SortBy) { case "productCategory": defaultQuery = defaultQuery.Where(x => x.Product.ProductCategoryId == query.SortById); break; case "productSubCategory": defaultQuery = defaultQuery.Where(x => x.Product.ProductSubCategoryId == query.SortById); break; } } int count = await defaultQuery.CountAsync(); return new PagedResponse<UserInventoryProductMapping> { Data = await defaultQuery .Skip((query.PageNumber - 1) * query.PageSize) .Take(query.PageSize) .ToListAsync(), PageNumber = query.PageNumber, PageSize = query.PageSize, TotalPages = (int)Math.Ceiling(count / (double)query.PageSize) }; } catch (Exception e) { _logger.LogError(e, e.Message); throw; } }
内容的提问来源于stack exchange,提问作者Runick z
相关产品推荐
相关产品推荐

