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

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");
        }
    }
}

关键修改点

  1. 将日志方法改为异步:把LogRequest改为LogRequestAsync并返回Task,内部所有数据库操作都通过await等待完成,避免线程阻塞。
  2. 移除中间件类的DbContext字段:直接在异步方法中传递DbContext,避免单例中间件的字段被多请求覆盖。
  3. 确保数据访问类支持异步:
    • 若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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 06:14:55