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

Blazor Server中使用EF Core关联查询时触发InvalidOperationException

问题:Blazor Server使用EF Core时出现DbContext并发操作错误

InvalidOperationException: A second operation was started on this context instance before a previous operation completed. This is usually caused by different threads concurrently using the same instance of DbContext.

我的Blazor Server项目中,在OnInitializedAsync方法里通过EF Core从多张表取数并关联数据,代码如下:

protected override async Task OnInitializedAsync()
{

    PeopleTypes = await DbContext.PeopleTypes.Select(pts => new PeopleType
        {
            Id = pts.Id,
            TypeName = pts.TypeName
        }).ToListAsync() ?? new List<PeopleType>();

    Peoples = await DbContext.People.Select(pl => new People
        {
            Id = pl.Id,
            Name = pl.Name,
            CompanyId = pl.CompanyId,
            TypeId = pl.TypeId
        }).ToListAsync() ?? new List<People>();

    CompanyLists = await DbContext.CompanyList.Select(cl => new CompanyList
        {
            Id = cl.Id,
            CompanyName = cl.CompanyName
        }).ToListAsync() ?? new List<CompanyList>();

    ClassDetails = await DbContext.ClassSessionsDetails
    .Where(csd => csd.CsID == CSId)
    .Where(csd => csd.CompanyId == selectedCompany)
    .Select(csd => new ClassSessionDetails
        {
            Id = csd.Id,
            CsID = csd.CsID
        }).ToListAsync();

    classMembersData = await DbContext.ClassSessionsDetails
        .Join(DbContext.ClassSessionsMembers,
            csd => csd.Id,
            csm => csm.CSD_Id,
            (csd, csm) => new { csd, csm })
        .Join(DbContext.People,
            combined => combined.csm.PeopleId,
            people => people.Id,
            (combined, people) => new { combined.csd, combined.csm, people })
        .GroupJoin(DbContext.CompanyList,
            combined => combined.people.CompanyId,
            company => company.Id,
            (combined, companies) => new { combined.csd, combined.csm, combined.people, company = companies.FirstOrDefault() })
        .GroupJoin(DbContext.PeopleTypes,
            combined => combined.people.TypeId,
            peopleType => peopleType.Id,
            (combined, peopleTypes) => new { combined.csd, combined.csm, combined.people, combined.company, peopleType = peopleTypes.FirstOrDefault() })
        .Select(result => new ClassMembers
            {
                PeopleId = result.people.Id,
                Name = result.people.Name,
                CompanyId = result.company.Id,
                CompanyName = result.company.CompanyName,
                PeopleTypeId = result.peopleType.Id,
                PeopleTypeName = result.peopleType.TypeName
            }).ToListAsync();

    await base.OnInitializedAsync();
}

求解决该错误的建议,或优化查询避免并发冲突的方法。


解决方案

一、错误根源

Blazor Server中默认注入的DbContext是Scoped生命周期,但Blazor组件生命周期与ASP.NET Core请求生命周期不同,同一个Scoped DbContext可能被多个异步操作同时访问,触发并发冲突。

二、具体解决方法

1. 改用Transient生命周期的DbContext

在Program.cs注册DbContext时,将Scoped改为Transient:

builder.Services.AddDbContext<YourDbContext>(options => 
    options.UseSqlServer(builder.Configuration.GetConnectionString("YourConnStr")), 
    ServiceLifetime.Transient);

每次获取DbContext都是全新实例,不会出现同一实例被并发操作的问题。

2. 在组件内创建独立DbContext实例

如果不想修改全局生命周期,可在组件中手动创建DbContext:

protected override async Task OnInitializedAsync()
{
    using var dbContext = new YourDbContext();
    
    // 所有数据库操作使用该局部dbContext实例
    PeopleTypes = await dbContext.PeopleTypes.Select(pts => new PeopleType
        {
            Id = pts.Id,
            TypeName = pts.TypeName
        }).ToListAsync() ?? new List<PeopleType>();
    
    // 其他查询同理替换DbContext为局部实例
}

需确保DbContext配置正确(如连接字符串)。

3. 并行执行异步查询(需用独立DbContext实例)

若想提升查询性能,可通过Task.WhenAll并行执行多个查询,但必须保证每个查询使用独立DbContext:

protected override async Task OnInitializedAsync()
{
    using var dbContext1 = new YourDbContext();
    using var dbContext2 = new YourDbContext();
    using var dbContext3 = new YourDbContext();
    using var dbContext4 = new YourDbContext();
    using var dbContext5 = new YourDbContext();

    var task1 = dbContext1.PeopleTypes.Select(pts => new PeopleType
        { Id = pts.Id, TypeName = pts.TypeName }).ToListAsync();
    var task2 = dbContext2.People.Select(pl => new People
        { Id = pl.Id, Name = pl.Name, CompanyId = pl.CompanyId, TypeId = pl.TypeId }).ToListAsync();
    var task3 = dbContext3.CompanyList.Select(cl => new CompanyList
        { Id = cl.Id, CompanyName = cl.CompanyName }).ToListAsync();
    var task4 = dbContext4.ClassSessionsDetails
        .Where(csd => csd.CsID == CSId && csd.CompanyId == selectedCompany)
        .Select(csd => new ClassSessionDetails { Id = csd.Id, CsID = csd.CsID }).ToListAsync();
    var task5 = dbContext5.ClassSessionsDetails
        .Join(DbContext.ClassSessionsMembers,
            csd => csd.Id, csm => csm.CSD_Id, (csd, csm) => new { csd, csm })
        .Join(DbContext.People,
            combined => combined.csm.PeopleId, people => people.Id, (combined, people) => new { combined.csd, combined.csm, people })
        .GroupJoin(DbContext.CompanyList,
            combined => combined.people.CompanyId, company => company.Id, (combined, companies) => new { combined.csd, combined.csm, combined.people, company = companies.FirstOrDefault() })
        .GroupJoin(DbContext.PeopleTypes,
            combined => combined.people.TypeId, peopleType => peopleType.Id, (combined, peopleTypes) => new { combined.csd, combined.csm, combined.people, combined.company, peopleType = peopleTypes.FirstOrDefault() })
        .Select(result => new ClassMembers
            {
                PeopleId = result.people.Id,
                Name = result.people.Name,
                CompanyId = result.company.Id,
                CompanyName = result.company.CompanyName,
                PeopleTypeId = result.peopleType.Id,
                PeopleTypeName = result.peopleType.TypeName
            }).ToListAsync();

    await Task.WhenAll(task1, task2, task3, task4, task5);

    PeopleTypes = task1.Result ?? new List<PeopleType>();
    Peoples = task2.Result ?? new List<People>();
    CompanyLists = task3.Result ?? new List<CompanyList>();
    ClassDetails = task4.Result;
    classMembersData = task5.Result;
}

三、查询优化建议

  1. 消除冗余查询:classMembersData的查询已经关联了CompanyList和PeopleTypes,完全不需要单独查询PeopleTypes、Peoples、CompanyLists三个集合,直接从关联查询中提取所需数据即可,减少数据库请求次数。
  2. 用Include/ThenInclude简化关联查询:EF Core支持通过导航属性的Include/ThenInclude实现关联,比手动Join更简洁易维护(前提是实体类已配置正确导航属性):
classMembersData = await dbContext.ClassSessionsDetails
    .Include(csd => csd.ClassSessionsMembers)
        .ThenInclude(csm => csm.People)
            .ThenInclude(p => p.CompanyList)
    .Include(csd => csd.ClassSessionsMembers)
        .ThenInclude(csm => csm.People)
            .ThenInclude(p => p.PeopleType)
    .Where(csd => csd.CsID == CSId && csd.CompanyId == selectedCompany)
    .SelectMany(csd => csd.ClassSessionsMembers)
    .Select(csm => new ClassMembers
    {
        PeopleId = csm.People.Id,
        Name = csm.People.Name,
        CompanyId = csm.People.CompanyList.Id,
        CompanyName = csm.People.CompanyList.CompanyName,
        PeopleTypeId = csm.People.PeopleType.Id,
        PeopleTypeName = csm.People.PeopleType.TypeName
    }).ToListAsync();

内容的提问来源于stack exchange,提问作者eza

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 22:34:56