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

异步调用场景下EF Core DbContext并发操作异常求助

解决EF Core中“同一上下文实例上启动了第二个操作”的问题

问题根源

  • 静态缓存cash跨所有UnitOfWork实例共享:首次调用GetRepository时,缓存绑定的是第一个DbContext的资源,后续UnitOfWork操作会意外访问该上下文,引发并发冲突。
  • 仓储方法异步操作未正确处理:Add/Remove/Update直接将ToArrayAsync()的Task存入缓存,既存在类型转换错误,又因未等待异步完成导致上下文并发操作。
  • async void引发的未等待问题:AddAsync使用async void,异步操作无法被追踪和等待,上下文可能在操作未完成时被释放。
  • Dispose实现错误:异步释放逻辑放在同步Dispose方法中,导致DbContext无法被正确异步释放。

修复后的代码

1. UnitOfWork(实例级缓存+异步释放)

public class UnitOfWork : IUnitOfWork, IAsyncDisposable
{
    private readonly ConcurrentDictionary<Type, object> _repsDictionary;
    // 改为实例级缓存,每个UoW独立维护
    private readonly ConcurrentDictionary<Type, IEnumerable<object>> _cash;
    private readonly BotContext _dbContext;

    public UnitOfWork()
    {
        _dbContext = new BotContext();
        _repsDictionary = new ConcurrentDictionary<Type, object>();
        _cash = new ConcurrentDictionary<Type, IEnumerable<object>>();
    }

    public IRepository<TEntity> GetRepository<TEntity>()
        where TEntity : class, IBaseEntity
    {
        return _repsDictionary.GetOrAdd(typeof(TEntity),
            x => new Repository<TEntity>(_dbContext.Set<TEntity>(), _cash)) as IRepository<TEntity>;
    }

    public async Task SaveChangesAsync()
    {
        Console.WriteLine($"Invoke SaveChangesAsync in context with id: {_dbContext.ContextId.InstanceId}");
        await _dbContext.SaveChangesAsync();
    }

    private bool _disposed;

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (_disposed) return;

        if (disposing)
        {
            _dbContext.Dispose();
        }

        _disposed = true;
    }

    // 实现IAsyncDisposable支持异步释放
    public async ValueTask DisposeAsync()
    {
        await DisposeAsync(true);
        GC.SuppressFinalize(this);
    }

    protected virtual async ValueTask DisposeAsync(bool disposing)
    {
        Console.WriteLine($"Dispose UoW (hash: {this.GetHashCode()}) with context id: {_dbContext.ContextId.InstanceId}");
        if (_disposed) return;

        if (disposing)
        {
            await _dbContext.DisposeAsync();
        }

        _disposed = true;
    }

    ~UnitOfWork() => Dispose(false);
}

2. Repository(修复异步逻辑+缓存绑定)

public class Repository<TEntity> : IRepository<TEntity> where TEntity : class, IBaseEntity
{
    private readonly DbSet<TEntity> _dbSet;
    private readonly ConcurrentDictionary<Type, IEnumerable<object>> _cash;

    public Repository(DbSet<TEntity> dbSet, ConcurrentDictionary<Type, IEnumerable<object>> cash)
    {
        _dbSet = dbSet;
        _cash = cash;
    }

    // 替换async void为async Task,确保异步操作可等待
    public async Task AddAsync(TEntity entity)
    {
        await _dbSet.AddAsync(entity);
        // 等待异步查询完成后再更新缓存
        var entities = await _dbSet.ToArrayAsync();
        _cash[typeof(TEntity)] = entities;
    }

    public async Task Add(TEntity entity)
    {
        _dbSet.Add(entity);
        var entities = await _dbSet.ToArrayAsync();
        _cash[typeof(TEntity)] = entities;
    }

    public async Task Remove(TEntity entity)
    {
        _dbSet.Remove(entity);
        var entities = await _dbSet.ToArrayAsync();
        _cash[typeof(TEntity)] = entities;
    }

    public async Task Update(TEntity entity)
    {
        _dbSet.Update(entity);
        var entities = await _dbSet.ToArrayAsync();
        _cash[typeof(TEntity)] = entities;
    }

    public IEnumerable<TEntity> GetAll()
    {
        if (_cash.TryGetValue(typeof(TEntity), out var obj))
        {
            return obj as IEnumerable<TEntity> ?? Enumerable.Empty<TEntity>();
        }
        // 缓存不存在时同步加载并缓存
        var entities = _dbSet.ToArray();
        _cash[typeof(TEntity)] = entities;
        return entities;
    }
}

3. 调用代码(异步释放+等待异步方法)

public class DALTestCommand : IConsoleCommand
{
    public string CommandName { get; set; } = "daltest";
    public string CommandDescription { get; set; } = "DAL Testing";
    public async Task Invoke(ControlConsole console, TelegramBotClient botClient)
    {
        // 使用await using支持异步释放UoW
        await using var uow = new UnitOfWork();
        var rep = uow.GetRepository<TelegramUser>();
        var entry = TelegramUser.Create(new Random().NextInt64(), "@TEST");
        // 等待仓储异步方法完成
        await rep.AddAsync(entry);
        await uow.SaveChangesAsync();
    }
}

关键修复总结

  • 移除静态共享缓存:每个UnitOfWork维护独立缓存,彻底避免跨上下文资源冲突。
  • 规范异步方法实现:禁用async void,所有异步操作返回Task并等待完成,确保上下文操作串行执行。
  • 正确实现异步释放:通过IAsyncDisposable接口异步释放DbContext,避免资源泄漏。
  • 移除静态字段:删除UnitOfWork中的静态_fieldDictionary,消除跨实例状态共享风险。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 19:07:02