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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 03:23:11