.NET DbContext并发操作InvalidOperationException异常求助
问题:DbContext并发操作异常解决
异常信息
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"
场景说明
当查询返回多条数据时触发异常,仅返回单条数据时服务正常。已将DataContext注入配置为ServiceLifetime.Scoped。
相关代码
服务层代码
public async Task<IQueryable<GetAllUsersDTO>> UsersById(Guid userId, CancellationToken cancellationToken) { try { var Log = await _db.Users .Include(x => x.University) .Include(x => x.Fields) .Where(x => x.UserId == userId) .OrderByDescending(x => x.CreatedAt) .AsNoTracking() .ToListAsync(); var logsDTO = await Task.WhenAll(Log.Select(async x => { var userInDb = await _db.Users.FindAsync((Guid)x.UserId); var user = await _userService.GetDirectoryInformation(userInDb, cancellationToken); return new GetAllUsersDTO() { Description = x.Description, PostDate = x.PostDate, Id = x.Id, RecieveUserId = x.RecieveUserId, UniversityId = x.UniversityId, IsVisible = x.IsVisible, IsApproved = x.IsApproved, UserIdOpen = x.UserId, UserName = user.UserName, LogTitle = x.LogTitle, LogNr = x.LogNr, Email = user.Email, }; })); return logsDTO.AsQueryable(); } catch (Exception ex) { Console.WriteLine("Error=", ex.Message); throw; } }
DataContext注入代码
private readonly DataContext _db; public UserService(DataContext context) : base(context) { _db = context; }
DI配置代码
builder.Services .AddDbContext<DataContext>(opt => opt.UseSqlServer(builder.Configuration.GetConnectionString("connString")) ,ServiceLifetime.Scoped );
解决方案
根本原因
Task.WhenAll会并行执行多个异步委托,每个委托里都调用_db.Users.FindAsync,导致同一个DbContext实例被多个线程同时操作,违反EF Core中DbContext的非线程安全特性。
修复方案
方案1:改用顺序执行,避免并行操作
把Task.WhenAll替换为顺序遍历,确保同一时间只有一个操作使用DbContext:
var logsDTO = new List<GetAllUsersDTO>(); foreach (var x in Log) { var userInDb = await _db.Users.FindAsync((Guid)x.UserId, cancellationToken); var user = await _userService.GetDirectoryInformation(userInDb, cancellationToken); logsDTO.Add(new GetAllUsersDTO() { Description = x.Description, PostDate = x.PostDate, Id = x.Id, RecieveUserId = x.RecieveUserId, UniversityId = x.UniversityId, IsVisible = x.IsVisible, IsApproved = x.IsApproved, UserIdOpen = x.UserId, UserName = user.UserName, LogTitle = x.LogTitle, LogNr = x.LogNr, Email = user.Email, }); }
方案2:预加载批量数据,减少重复查询
优化初始查询逻辑,一次性获取所有需要的用户信息,避免循环内重复访问数据库:
// 提取所有需要查询的用户ID并去重 var userIds = Log.Select(x => x.UserId).Distinct().ToList(); // 一次性查询所有目标用户 var usersInDb = await _db.Users.Where(u => userIds.Contains(u.UserId)).ToListAsync(cancellationToken); // 遍历转换DTO,无需再循环查询数据库 var logsDTO = new List<GetAllUsersDTO>(); foreach (var x in Log) { var userInDb = usersInDb.First(u => u.UserId == x.UserId); var user = await _userService.GetDirectoryInformation(userInDb, cancellationToken); logsDTO.Add(new GetAllUsersDTO() { Description = x.Description, PostDate = x.PostDate, Id = x.Id, RecieveUserId = x.RecieveUserId, UniversityId = x.UniversityId, IsVisible = x.IsVisible, IsApproved = x.IsApproved, UserIdOpen = x.UserId, UserName = user.UserName, LogTitle = x.LogTitle, LogNr = x.LogNr, Email = user.Email, }); }
方案3:使用DbContext工厂创建独立实例(适合必须并行的场景)
如果业务需要保持并行执行,可以通过IDbContextFactory为每个并行任务创建独立的DbContext实例:
- 修改DI配置:
builder.Services.AddDbContextFactory<DataContext>(opt => opt.UseSqlServer(builder.Configuration.GetConnectionString("connString")) );
- 更新服务层代码:
private readonly IDbContextFactory<DataContext> _dbContextFactory; private readonly IUserService _userService; public UserService(IDbContextFactory<DataContext> dbContextFactory, IUserService userService) { _dbContextFactory = dbContextFactory; _userService = userService; } public async Task<IQueryable<GetAllUsersDTO>> UsersById(Guid userId, CancellationToken cancellationToken) { try { using var db = _dbContextFactory.CreateDbContext(); var Log = await db.Users .Include(x => x.University) .Include(x => x.Fields) .Where(x => x.UserId == userId) .OrderByDescending(x => x.CreatedAt) .AsNoTracking() .ToListAsync(cancellationToken); var logsDTO = await Task.WhenAll(Log.Select(async x => { using var dbParallel = _dbContextFactory.CreateDbContext(); var userInDb = await dbParallel.Users.FindAsync((Guid)x.UserId, cancellationToken); var user = await _userService.GetDirectoryInformation(userInDb, cancellationToken); return new GetAllUsersDTO() { Description = x.Description, PostDate = x.PostDate, Id = x.Id, RecieveUserId = x.RecieveUserId, UniversityId = x.UniversityId, IsVisible = x.IsVisible, IsApproved = x.IsApproved, UserIdOpen = x.UserId, UserName = user.UserName, LogTitle = x.LogTitle, LogNr = x.LogNr, Email = user.Email, }; })); return logsDTO.AsQueryable(); } catch (Exception ex) { Console.WriteLine("Error=", ex.Message); throw; } }
注意事项
- EF Core的
DbContext实例本身非线程安全,禁止在多个并发线程中共享同一实例。 - 优先选择预加载、批量查询的方案,既能避免并发问题,也能提升数据库访问性能。
- 若必须使用并行操作,务必通过
IDbContextFactory为每个线程创建独立的DbContext实例。
内容的提问来源于stack exchange,提问作者leoxrmd
相关产品推荐
相关产品推荐

