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; }
三、查询优化建议
- 消除冗余查询:
classMembersData的查询已经关联了CompanyList和PeopleTypes,完全不需要单独查询PeopleTypes、Peoples、CompanyLists三个集合,直接从关联查询中提取所需数据即可,减少数据库请求次数。 - 用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
相关产品推荐
相关产品推荐

