ASP.NET Core中间件EF Core报错:上下文实例存在并发操作
问题分析与解决方案
错误根源
你遇到的System.InvalidOperationException是因为EF Core的DbContext并非线程安全,而代码中存在未使用异步await的同步数据库操作,导致同一个DbContext实例被多个并发操作访问。另外,中间件属于单例生命周期,虽然你在Invoke中注入了Scoped的DbContext,但同步操作会阻塞线程,使得后续请求或当前请求的后续中间件复用同一个DbContext时引发冲突。
简单解决方案
核心是将所有数据库操作改为异步并正确使用await,同时避免在单例中间件中存储Scoped的DbContext实例。
修改后的中间件代码
public class HTTPTrafficLogger { private readonly RequestDelegate _next; public HTTPTrafficLogger(RequestDelegate next) { _next = next; } public async Task Invoke(HttpContext httpContext, CustomDbContext dbContext) { // 异步执行日志记录 await LogRequestAsync(httpContext, dbContext); await _next(httpContext); } private async Task LogRequestAsync(HttpContext httpContext, CustomDbContext dbContext) { try { StringBuilder requestContent = new StringBuilder(); requestContent.AppendLine($"Protocol: {httpContext.Request.Protocol}"); requestContent.AppendLine($"Method: {httpContext.Request.Method.ToUpper()}"); requestContent.AppendLine($"Scheme: {httpContext.Request.Scheme}"); requestContent.AppendLine($"PathBase: {httpContext.Request.PathBase}"); requestContent.AppendLine($"Path: {httpContext.Request.Path}"); foreach (var (headerKey, headerValue) in httpContext.Request.Headers) { requestContent.AppendLine($"{headerKey}: {headerValue}"); } AuthenticationTokenProperties authenticationTokenProperties = JWT.GetUserAuthenticationTokenProperties(httpContext.User.Identity as ClaimsIdentity); AuditDto HTTPRequestAuditDto = new AuditDto() { UserEmailAddress = string.IsNullOrEmpty(authenticationTokenProperties.UserEmail) ? string.Empty : authenticationTokenProperties.UserEmail, ActionName = httpContext.Request.Path.ToString(), ModelName = "HTTPLoggerMiddleware", }; // 使用异步方法并await,确保数据库操作完成后再继续 var auditCore = new AuditCore(dbContext, null); await auditCore.AddAsync(HTTPRequestAuditDto); } catch (Exception ex) { var systemLogCore = new SystemLogCore(dbContext, null); await systemLogCore.AddAsync(ex, "", "HTTPTrafficLogger"); } } }
关键修改点
- 将日志方法改为异步:把
LogRequest改为LogRequestAsync并返回Task,内部所有数据库操作都通过await等待完成,避免线程阻塞。 - 移除中间件类的DbContext字段:直接在异步方法中传递DbContext,避免单例中间件的字段被多请求覆盖。
- 确保数据访问类支持异步:
- 若
AuditCore和SystemLogCore没有异步版本,需要修改它们的Add方法,使用EF Core的异步API,示例如下:// AuditCore的AddAsync示例 public async Task AddAsync(AuditDto dto) { var auditEntity = MapDtoToEntity(dto); await _dbContext.Audits.AddAsync(auditEntity); await _dbContext.SaveChangesAsync(); } - 绝对禁止在异步代码中使用
.Result或.Wait()阻塞线程,这会直接导致DbContext被并发访问。
- 若
为什么之前的尝试无效
- 改成Transient生命周期没用:问题根源不是DbContext的生命周期,而是同步操作导致的线程阻塞,使得同一个DbContext实例被多个线程同时操作。
- 改异步但无效:大概率是没有正确
await异步方法,或者数据访问类内部仍然使用同步操作。
内容的提问来源于stack exchange,提问作者See
相关产品推荐
相关产品推荐

