.NET Core 8多服务共享DbContext引发并发操作异常的解决方法
问题分析与解决方案
核心原因
你的推测是对的:默认情况下AddDbContext注册的DbContext是Scoped生命周期(Blazor Server中与用户电路绑定),当NavMenu和当前页面的OnInitializedAsync同时触发,两个服务会共享同一个DbContext实例,而DbContext本身不是线程安全的,SqlServer的ADO.NET提供程序会严格检测这种并发操作并抛出异常;Sqlite因本地文件特性允许有限并发读取,所以未触发问题。
正确处理方案(按推荐优先级排序)
1. 确保所有DbContext操作异步且正确await
先检查你的NavService和PageService中的数据操作:
- 必须使用EF Core的异步API(如
ToListAsync()、FirstOrDefaultAsync()、SaveChangesAsync()) - 所有异步操作都要通过
await等待完成,避免出现未等待的异步调用(比如直接调用dbContext.SomeQuery.AsAsyncEnumerable()而不await,或同步调用阻塞线程)
示例正确写法:
public async Task<List<NavItem>> GetNavItemsAsync() { return await _dbContext.NavItems.Where(n => n.IsActive).ToListAsync(); }
2. 检查服务的生命周期注册
如果你的NavService或PageService被注册为Singleton,会导致Scoped的DbContext被Singleton服务长期持有,变成事实上的Singleton,必然引发并发问题。确保服务注册为Scoped或Transient:
// 正确注册方式 builder.Services.AddScoped<NavService>(); builder.Services.AddScoped<PageService>();
3. 使用IDbContextFactory创建独立DbContext实例
这是Blazor场景下处理DbContext并发的推荐方案,无需修改DbContext的默认生命周期:
- 注册DbContext工厂:
builder.Services.AddDbContextFactory<ApplicationDbContext>(options => options.UseSqlServer(connectionString));
- 在服务中注入工厂,每次操作创建新的DbContext实例:
public class PageService { private readonly IDbContextFactory<ApplicationDbContext> _dbContextFactory; public PageService(IDbContextFactory<ApplicationDbContext> dbContextFactory) { _dbContextFactory = dbContextFactory; } public async Task<PageContent> GetPageContentAsync(int pageId) { using var dbContext = _dbContextFactory.CreateDbContext(); return await dbContext.PageContents.FirstOrDefaultAsync(p => p.Id == pageId); } }
这种方式保证每个数据操作都使用独立的DbContext实例,从根源避免并发冲突。
4. 不推荐:将DbContext设为Transient
虽然这能解决问题,但会导致DbContext实例过度创建,失去EF Core的缓存和跟踪优势,仅在极端场景下考虑使用。
为什么Sqlite未触发异常?
Sqlite的默认配置允许同一个连接上的并发读取操作(写入仍需串行),而SqlServer的ADO.NET提供程序会严格限制同一个DbContext实例同时执行多个操作,因为SqlServer的连接模型不支持这种并发行为。
内容的提问来源于stack exchange,提问作者AndreiM
相关产品推荐
相关产品推荐

