.NET8中DbContext并发操作异常排查及解决方案咨询
问题分析与解决方案
核心问题根源
- DbContext使用逻辑错误:配置了
DbContextFactory却未正确利用,反而直接注入RepositoryContext到RepositoryWrapper,且Wrapper内部存在字段不匹配问题(构造函数赋值_repo1/_repo2/_repo3,但属性返回未赋值的_repo),导致上下文实例可能被意外共享。 - Blazor组件生命周期冲突:Blazor Server组件为Scoped生命周期,若
RepositoryWrapper被组件缓存为私有字段,会导致同一上下文实例被多个异步操作并发调用,触发DbContext线程安全异常。 - 短生命周期原则未落地:DbContext本身非线程安全,必须保证每个独立操作(尤其是异步操作)使用专属实例,或同一实例上的操作严格串行执行。
优雅解决方案
1. 重构RepositoryWrapper,改用DbContextFactory创建上下文
放弃直接注入RepositoryContext,改为注入IDbContextFactory<RepositoryContext>,在需要数据库操作时动态创建上下文实例,用完自动释放。
保留Program.cs的Factory配置
builder.Services.AddDbContextFactory<RepositoryContext>(o => o.UseSqlServer(builder.Configuration.GetConnectionString("Usdaw")), ServiceLifetime.Transient); builder.Services.AddTransient<IRepositoryWrapper, RepositoryWrapper>();
重构RepositoryWrapper代码
private readonly IDbContextFactory<RepositoryContext> _contextFactory; private readonly IConfiguration _config; // 移除多余的RepositoryContext参数,仅注入工厂和配置 public RepositoryWrapper(IConfiguration config, IDbContextFactory<RepositoryContext> contextFactory) { _config = config; _contextFactory = contextFactory; } // 封装独立数据库操作,每个操作使用专属上下文 public async Task<ActivityLog?> GetActivityLogBySessionId(string sessionId) { using var context = _contextFactory.CreateDbContext(); return await context.ActivityLogs .Where(s => s.SessionID == Guid.Parse(sessionId)) .FirstOrDefaultAsync(); } // 多操作共享上下文时,确保串行执行 public async Task AddActivityLog(ActivityLog log) { using var context = _contextFactory.CreateDbContext(); context.ActivityLogs.Add(log); await context.SaveChangesAsync(); }
2. 修正RepositoryContext构造函数笔误
原代码中构造函数名称错误(ERepositoryContext应为RepositoryContext),修正后确保上下文能正确初始化:
public partial class RepositoryContext : DbContext { public RepositoryContext() { } public RepositoryContext(DbContextOptions<RepositoryContext> options) : base(options) { } public virtual DbSet<ActivityLog> ActivityLogs { get; set; } = null!; }
3. Blazor组件中避免缓存RepositoryWrapper
在Blazor Server组件中,不要将IRepositoryWrapper保存为组件私有字段,而是在每个方法中直接使用注入的实例:
@inject IRepositoryWrapper Wrapper <button @onclick="LoadSessionLog">加载会话日志</button> @code { private async Task LoadSessionLog() { var log = await Wrapper.GetActivityLogBySessionId(HttpContext.Session.Id); // 处理业务逻辑 } }
4. IIS应用池回收排查
应用池回收本身不会直接触发该异常,但回收时若存在未完成的异步数据库操作,可能导致上下文状态异常。确保所有DbContext实例通过using语句自动释放,避免资源泄漏。
内容的提问来源于stack exchange,提问作者Ian Jowett
相关产品推荐
相关产品推荐

