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

C# WebApi多请求复用同一DbContext引发并发异常问题排查

解决ASP.NET Core Web API中DbContext并发操作异常问题

问题场景

基于ASP.NET Core Web API模板+EF7构建的接口,单请求运行正常,但多请求并发时抛出System.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.

例如启动项目后,Swagger文档页并行请求多个JS/CSS文件时触发异常。期望每个请求对应独立的DbContext实例,但实际未实现。

当前DbContext注册方式:

webAppBuilder.Services.AddDbContext<IDbContext, MyDbContext>(
      options => options.UseSqlServer(
            webAppBuilder.Configuration.GetConnectionString("Default"),
            x => x.UseNetTopologySuite()
       )
);

尝试修改生命周期为Transient/Singleton均无效。

中间件代码(构造注入DbContext):

public MyMiddleware(RequestDelegate next, IWebHostEnvironment env, IDbContext dbContext, Configuration configuration)
{
   Env = env;
   Next = next;
   RecyclableMemoryStreamManager = new RecyclableMemoryStreamManager();
   DbContext = dbContext;
   Configuration = configuration;
}

public async Task InvokeAsync(HttpContext context)
{
   // 从context获取api密钥
   var apiKey = ...;

   var apiKeyInformation = DbContext.ApiKeys.FirstOrDefault(x => x.ApiKey == apiKey);

   // 处理api密钥逻辑
   ...
   await Next(context);
}

中间件注册:

webApplication.UseMiddleware<MyMiddleware>();

根因分析

ASP.NET Core中,中间件默认是单例生命周期——程序启动时只会创建一次实例。如果在中间件的构造函数中注入DbContext,无论DbContext本身注册的是Scoped/Transient/Singleton,都会被绑定到中间件的单例实例上,导致所有请求共用同一个DbContext。而DbContext设计为非线程安全,并发操作自然会抛出异常。

解决方案

方案1:在InvokeAsync中获取当前请求的Scoped DbContext

不在构造函数注入DbContext,而是在每次请求的InvokeAsync方法中,通过HttpContext.RequestServices获取当前请求范围内的DbContext实例:

修改后的中间件代码:

public MyMiddleware(RequestDelegate next, IWebHostEnvironment env, Configuration configuration)
{
   Env = env;
   Next = next;
   RecyclableMemoryStreamManager = new RecyclableMemoryStreamManager();
   Configuration = configuration;
}

public async Task InvokeAsync(HttpContext context)
{
   // 获取当前请求的Scoped DbContext
   using var dbContext = context.RequestServices.GetRequiredService<IDbContext>();

   // 从context获取api密钥
   var apiKey = ...;

   var apiKeyInformation = dbContext.ApiKeys.FirstOrDefault(x => x.ApiKey == apiKey);

   // 处理api密钥逻辑
   ...
   await Next(context);
}

该方式利用了RequestServices的Scoped特性,每个请求都会拿到独立的DbContext实例,完美适配并发场景。

方案2:使用DbContext工厂

注册DbContext工厂,通过工厂在每次请求时创建新的DbContext实例:

  1. 修改DbContext注册方式为工厂模式:
webAppBuilder.Services.AddDbContextFactory<IDbContext, MyDbContext>(
      options => options.UseSqlServer(
            webAppBuilder.Configuration.GetConnectionString("Default"),
            x => x.UseNetTopologySuite()
       )
);
  1. 修改中间件代码,注入工厂并创建实例:
private readonly IDbContextFactory<IDbContext> _dbContextFactory;

public MyMiddleware(RequestDelegate next, IWebHostEnvironment env, IDbContextFactory<IDbContext> dbContextFactory, Configuration configuration)
{
   Env = env;
   Next = next;
   RecyclableMemoryStreamManager = new RecyclableMemoryStreamManager();
   _dbContextFactory = dbContextFactory;
   Configuration = configuration;
}

public async Task InvokeAsync(HttpContext context)
{
   // 创建新的DbContext实例
   using var dbContext = _dbContextFactory.CreateDbContext();

   // 从context获取api密钥
   var apiKey = ...;

   var apiKeyInformation = dbContext.ApiKeys.FirstOrDefault(x => x.ApiKey == apiKey);

   // 处理api密钥逻辑
   ...
   await Next(context);
}

工厂模式会确保每次调用CreateDbContext()都生成新实例,避免并发冲突。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 17:11:15