You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 01:55:10