ASP.NET Core MVC二次请求触发DbContext并发操作异常排查
场景复现
开发ASP.NET Core MVC应用时,前端通过AJAX发送POST请求切换广告商激活状态,首次请求正常,第二次请求抛出以下异常:
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.
重启项目后可再次正常执行一次请求,第二次仍报错。已检查代码中异步操作均使用await,但问题依旧。
相关代码
前端AJAX请求
$.ajax({ url:'/Advertisers/ActiveAdvertiser?id='+id+'&isActive='+!isActive, method: 'POST', success: function(r){ Swal.fire("Inactivated!", "Advertiser inactivated successfully", "success"); }, error: function (request) { console.log(request.responseText) Swal.fire("Error!", "Something went wrong, please try again`", "warning"); } });
控制器代码
[HttpPost] public async Task<JsonResult> ActiveAdvertiser(int id, bool isActive) { var advertiser = await _advertisersService.GetAdvertiserByAdvertiserIdAsync(id); if (advertiser != null) { var model = AssingAdvertiserViewModel(advertiser, id); model.IsActive = isActive; var result = await _advertisersService.UpdateAdvertiserAsync(model, GetCurrentUserAsync().Id); if (result != null) { return Json(new { result = "OK" }); } } return Json(new { result = "BadRequest" }); }
服务层代码
public Task<Advertiser?> GetAdvertiserByAdvertiserIdAsync(int advertiserId) { return _db.Advertisers .Include(a => a.Address) .Include(pc => pc.PrimaryContact) .Include(ac => ac.AlternateContact) .FirstOrDefaultAsync(x => x.AdvertiserId == advertiserId); } private AdvertiserViewModel AssingAdvertiserViewModel(Advertiser advertiser, int id) { var model = new AdvertiserViewModel() { //Fill model here }; return model; } public async Task<Advertiser?> UpdateAdvertiserAsync(AdvertiserViewModel model, int updatedById) { var advertiser = await GetAdvertiserByAdvertiserIdAsync(model.AdvertiserId); if (advertiser is null) return null; advertiser.Name = model.Name; // fill model here await _db.SaveChangesAsync(); return advertiser; }
问题排查与解决方案
核心原因
异常本质是DbContext实例被多个请求并发复用。ASP.NET Core中DbContext默认应为Scoped生命周期(每个请求创建一个实例),若DbContext或服务层类被注册为Singleton,会导致所有请求共用同一个DbContext实例,触发并发操作冲突。
具体排查与修复点
检查DbContext注册方式
确保在Program.cs(或Startup.cs)中DbContext按Scoped注册,正确写法:builder.Services.AddDbContext<YourDbContext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));(
AddDbContext默认即为Scoped生命周期)检查服务层类注册方式
若AdvertisersService被注册为Singleton(如使用AddSingleton),会长期持有同一个DbContext实例。需改为Scoped或Transient:builder.Services.AddScoped<IAdvertisersService, AdvertisersService>();修正
GetCurrentUserAsync()调用
控制器中GetCurrentUserAsync().Id存在异步操作未完成就访问结果的问题,需改为:(await GetCurrentUserAsync()).Id避免获取到
Task对象的Id而非实际用户Id,同时保证异步流程完整。
验证
修改注册方式与代码后,重启应用,多次发送请求即可解决DbContext并发操作异常。
内容的提问来源于stack exchange,提问作者Jesus

