DbContext并发访问异常求助(.NET6+SQL Server)
问题场景
使用.NET6与Microsoft SQL Server环境,编写查询代码如下:
var user = await DbContext.Set<ApplicationUser>() .FirstOrDefaultAsync(x => x.UserID.ToUpper() == userID.ToUpper());
并发请求时抛出异常:
System.InvalidOperationException
HResult=0x80131509
Message=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.
Source=Microsoft.EntityFrameworkCore
StackTrace:
at Microsoft.EntityFrameworkCore.Infrastructure.Internal.ConcurrencyDetector.EnterCriticalSection()
at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable1.AsyncEnumerator.<MoveNextAsync>d__18.MoveNext() at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Microsoft.EntityFrameworkCore.Query.ShapedQueryCompilingExpressionVisitor.<SingleOrDefaultAsync>d__151.MoveNext()
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at Microsoft.EntityFrameworkCore.Query.ShapedQueryCompilingExpressionVisitor.d__15 1.MoveNext() at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Runtime.CompilerServices.TaskAwaiter1.GetResult()
at SF.Auth.Infrastructure.Data.RBA.Repository.IdentityManagerRepository.d__8.MoveNext() in C:\Users\Bel12\source\repos\sf-identity\SF.Auth.Infrastructure\Data\RBA\Repository\IdentityManagerRepository.cs:line 77
Startup中DbContext与Repository配置:
services.AddDbContext<UserManagementDbContext>(options => options .UseSqlServer(Configuration.GetConnectionString("home_sql_context")), ServiceLifetime.Transient, ServiceLifetime.Transient); services.AddTransient<IIdentityManagerRepository, IdentityManagerRepository>();
已通过JMeter并发请求复现异常。
解决方案
1. 规范Repository的DbContext注入方式
确保Repository通过构造函数注入获取DbContext,禁止手动实例化或用静态字段存储DbContext,避免实例共享。正确示例:
public class IdentityManagerRepository : IIdentityManagerRepository { private readonly UserManagementDbContext _dbContext; // 构造函数注入,保证每个Repository实例对应独立的DbContext public IdentityManagerRepository(UserManagementDbContext dbContext) { _dbContext = dbContext; } public async Task<ApplicationUser> GetUser(string userID) { return await _dbContext.Set<ApplicationUser>() .FirstOrDefaultAsync(x => x.UserID.ToUpper() == userID.ToUpper()); } }
2. 调整DbContext服务生命周期配置
ASP.NET Core中DbContext默认的Scoped生命周期(每个请求对应一个实例)是最安全的并发配置,无需手动指定Transient。修改Startup配置:
services.AddDbContext<UserManagementDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("home_sql_context")));
若坚持使用Transient,需确保无任何单例/静态对象引用DbContext实例。
3. 避免异步操作中混用同步方法
检查代码中是否存在未正确等待异步任务的情况(如用Task.Result、Task.Wait()),或同步调用EF Core方法(如FirstOrDefault而非FirstOrDefaultAsync),这类操作会导致线程阻塞,触发上下文并发冲突。确保所有EF Core操作都使用异步方法并正确await。
4. 排查单例服务的依赖问题
若存在单例服务依赖你的Repository(或直接依赖DbContext),单例会长期持有DbContext实例,导致多个请求共享同一实例。检查服务注册,确保依赖DbContext的服务生命周期不超过Scoped或Transient。
5. 优化查询逻辑
x.UserID.ToUpper() == userID.ToUpper()会导致SQL Server无法使用索引,并发场景下加剧性能问题和上下文冲突。建议:
- 将数据库中UserID字段设置为不区分大小写的排序规则(如
SQL_Latin1_General_CP1_CI_AS) - 修改查询代码:
return await _dbContext.Set<ApplicationUser>() .FirstOrDefaultAsync(x => x.UserID == userID);
内容的提问来源于stack exchange,提问作者Betsegaw Lemma Amersho

