ASP.NET Core中使用EF Core更新实体时的并发问题处理方案
处理EF Core中Med实体更新的并发冲突问题
针对你当前UpdateMedAsync方法的并发风险,EF Core提供两种主流解决方案,下面分别说明实现方式:
一、乐观并发控制(推荐,无锁机制)
这是EF Core官方推荐的方案,通过**行版本号(RowVersion)**检测并发冲突,适合绝大多数场景。
1. 给Med实体添加并发标记
在Med类中新增RowVersion属性,用于存储数据库自动维护的行版本:
public class Med { public int Id { get; set; } public string Name { get; set; } public int Count { get; set; } // 其他现有属性... [Timestamp] public byte[] RowVersion { get; set; } }
如果使用Fluent API配置(可选,替代特性):
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Med>() .Property(m => m.RowVersion) .IsRowVersion(); }
2. 修改UpdateMedAsync方法
移除ChangeTracker.Clear()(Scoped生命周期的DbContext无需手动清除跟踪),并捕获并发异常处理:
public async Task UpdateMedAsync(Med med) { if (med == null) throw new ArgumentNullException(nameof(med)); try { _context.Meds.Update(med); await _context.SaveChangesAsync(); } catch (DbUpdateConcurrencyException ex) { // 检查实体是否已被删除 var existingMed = await _context.Meds.FindAsync(med.Id); if (existingMed == null) { throw new InvalidOperationException("该药品已被其他用户删除"); } // 抛出明确的并发异常,让上层(比如API控制器)处理重试逻辑 throw new InvalidOperationException("该药品已被其他用户修改,请刷新数据后重试", ex); } }
3. 前端配合(关键)
更新接口需要将RowVersion返回给前端,前端在提交更新请求时,必须将该版本号一同传回服务端。这样EF Core会自动对比数据库中的行版本,不一致则触发DbUpdateConcurrencyException。
二、悲观并发控制(适合高冲突场景)
如果你的系统中药品更新冲突非常频繁,可以使用悲观锁直接锁定目标行,阻止其他事务修改,直到当前事务完成。
修改UpdateMedAsync方法(带事务和行锁)
public async Task UpdateMedAsync(int id, Med updatedMed) { if (updatedMed == null) throw new ArgumentNullException(nameof(updatedMed)); using var transaction = await _context.Database.BeginTransactionAsync(); try { // 使用UPDLOCK+HOLDLOCK锁定行,直到事务提交/回滚 var existingMed = await _context.Meds .FromSqlRaw("SELECT * FROM Meds WITH (UPDLOCK, HOLDLOCK) WHERE Id = {0}", id) .FirstOrDefaultAsync(); if (existingMed == null) { throw new KeyNotFoundException("未找到指定药品"); } // 仅更新需要修改的字段(避免覆盖未提交的其他变更) existingMed.Name = updatedMed.Name; existingMed.Count = updatedMed.Count; existingMed.IngredientId = updatedMed.IngredientId; // 其他需要更新的字段... await _context.SaveChangesAsync(); await transaction.CommitAsync(); } catch { await transaction.RollbackAsync(); throw; } }
额外优化建议
- 避免直接更新传入的实体:优先从数据库获取最新实体,再选择性更新字段,防止覆盖其他用户的隐性变更。
- Scoped生命周期的DbContext:每个请求对应一个DbContext实例,无需手动调用
ChangeTracker.Clear(),否则会丢失跟踪状态导致并发检测失效。
内容的提问来源于stack exchange,提问作者MogliMehmet
相关产品推荐
相关产品推荐

