Blazor Server侧边栏与首页同时调用数据库引发DbContext并发异常
问题解决:Blazor Server中DbContext并发操作异常
问题根源
Blazor Server中Scoped生命周期的DbContext会被多个组件共享,当登录后侧边栏TreeView与首页卡片同时发起异步数据库查询时,同一DbContext实例被多个线程并发调用,触发InvalidOperationException异常。未登录时用内存生成菜单、刷新后缓存生效无问题,也验证了这是并发查询导致的冲突。
具体解决办法
方案1:使用DbContext工厂创建独立实例(推荐)
替换直接注入DbContext的方式,改用IDbContextFactory每次查询时创建新的DbContext实例,从根源避免并发冲突:
1.1 改造MenuUtilities
public class MenuUtilities { private readonly IDbContextFactory<YourDbContext> _dbContextFactory; public MenuUtilities(IDbContextFactory<YourDbContext> dbContextFactory) { _dbContextFactory = dbContextFactory; } public async Task<List<MenuItem>> GetMenuItemsAsync() { // 每次查询创建独立的DbContext实例 using var context = _dbContextFactory.CreateDbContext(); return await context.MenuItems.Where(m => m.IsActive).ToListAsync(); } }
1.2 配置Program.cs
// 替换原有的AddDbContext,改用AddDbContextFactory builder.Services.AddDbContextFactory<YourDbContext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
方案2:给菜单查询添加异步锁
如果不想修改DbContext注入方式,可以通过SemaphoreSlim确保同一时间只有一个线程执行数据库查询,同时结合缓存逻辑减少重复查询:
public class MenuUtilities { private readonly YourDbContext _context; private readonly SemaphoreSlim _menuQueryLock = new SemaphoreSlim(1, 1); private List<MenuItem> _cachedMenu; public MenuUtilities(YourDbContext context) { _context = context; } public async Task<List<MenuItem>> GetMenuItemsAsync() { // 先检查缓存 if (_cachedMenu != null) return _cachedMenu; await _menuQueryLock.WaitAsync(); try { // 二次检查缓存,避免锁等待期间已有其他线程完成查询 if (_cachedMenu != null) return _cachedMenu; _cachedMenu = await _context.MenuItems.Where(m => m.IsActive).ToListAsync(); return _cachedMenu; } finally { _menuQueryLock.Release(); } } }
方案3:调整组件加载时机(临时 workaround)
让其中一个组件延迟发起菜单请求,错开并发时间:
<!-- 首页卡片组件 --> @code { private List<MenuItem> _cardMenuItems; protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { // 延迟100ms,等待侧边栏菜单查询完成 await Task.Delay(100); _cardMenuItems = await _menuUtilities.GetMenuItemsAsync(); StateHasChanged(); } } }
排查验证要点
- 检查
MenuUtilities中是否复用了同一个DbContext实例,有没有混用同步/异步查询方法(比如用ToList()而非ToListAsync()) - 确认缓存逻辑是否在首次查询后正确写入,避免刷新后仍重复发起数据库请求
- 验证DbContext的生命周期配置,确保没有错误地注册为Singleton
内容的提问来源于stack exchange,提问作者tvbRPTI2754
相关产品推荐
相关产品推荐

