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

.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实例:

  1. 修改DI配置:
builder.Services.AddDbContextFactory<DataContext>(opt =>
    opt.UseSqlServer(builder.Configuration.GetConnectionString("connString"))
);
  1. 更新服务层代码:
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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 00:13:14