异步调用场景下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
相关产品推荐
相关产品推荐

