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

EF Core无实际更新时SaveChanges抛出并发异常解决方案

问题场景

从表单接收用户可调整的个人数据时,无法提前判断用户是否修改了内容,常规处理逻辑是将提交的数据映射到数据库关联模型后执行更新。但如果用户仅点击「保存」按钮未修改任何内容,执行时会抛出如下异常:

Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException: 'Database operation expected to affect 1 row(s) but actually affected 0 row(s).'

原有实现代码如下:

public int relatie_update(relatieBeheer rel)
{
    var db = new DataContext(_configuration);

    relData relatie = this.relatie_get(rel.id_rel); // 从数据库获取已存在的关联记录
    relatie.update(rel); // 将前端传入的relatieBeheer模型字段映射到数据库实体relData
    db.relatie.Update(relatie);

    db.SaveChanges();

    return rel.id_rel;
}

核心疑问:有没有简洁优雅的方案解决这个问题?是否必须手动校验relData实例的字段变更,再根据校验结果决定是否调用Update()方法?

问题原因

异常的根源是显式调用db.relatie.Update(relatie)时,EF Core会无视实际字段值变化,把实体所有字段都标记为「已修改」状态,最终生成的UPDATE语句会带上并发校验条件。如果所有字段值和数据库完全一致,数据库执行UPDATE语句时返回的受影响行数为0,就会触发这个并发异常。

解决方案

不需要手动逐字段写校验逻辑,EF Core本身的变更跟踪能力已经内置了字段值对比能力,根据代码结构选对应方案即可:

  • 最推荐方案:移除显式Update调用,利用EF Core自动变更跟踪
    调整查询逻辑,直接在当前DataContext实例中查询被跟踪的目标实体,去掉显式的Update()调用。EF Core会自动跟踪实体属性的变动:没有任何字段修改时,SaveChanges()不会生成任何数据库更新语句,完全不会触发异常;有字段修改时只会更新实际变动的字段,执行效率更高。
    调整后代码参考:
    public int relatie_update(relatieBeheer rel)
    {
        var db = new DataContext(_configuration);
        // 直接在当前上下文查询被跟踪的实体,可根据需要把原有relatie_get的过滤逻辑迁移过来
        relData relatie = db.relatie.FirstOrDefault(r => r.id_rel == rel.id_rel);
        // 补充非空校验,兼容记录被删除的异常场景
        if (relatie == null) throw new KeyNotFoundException("待更新的记录不存在");
        relatie.update(rel);
        // 移除显式Update调用,变更跟踪自动处理状态标记
        db.SaveChanges();
        return rel.id_rel;
    }
    
  • 兼容现有逻辑方案:附加实体后自动重置未变更字段的状态
    如果要保留现有relatie_get的封装逻辑(比如方法内置了通用查询过滤、权限校验不好改动),可以在映射完字段后,附加实体到上下文,自动把值未发生变化的属性重置为未修改状态,没有实际变更时直接跳过保存:
    public int relatie_update(relatieBeheer rel)
    {
        var db = new DataContext(_configuration);
        relData relatie = this.relatie_get(rel.id_rel);
        if (relatie == null) throw new KeyNotFoundException("待更新的记录不存在");
        relatie.update(rel);
        
        db.relatie.Attach(relatie);
        db.Entry(relatie).State = EntityState.Modified;
        // 遍历属性,把值和原始值一致的属性标记为未修改
        foreach (var prop in db.Entry(relatie).Properties)
        {
            if (prop.IsModified && (prop.OriginalValue?.Equals(prop.CurrentValue) ?? prop.CurrentValue == null))
            {
                prop.IsModified = false;
            }
        }
        // 存在实际变更才执行保存
        if (db.Entry(relatie).Properties.Any(p => p.IsModified))
        {
            db.SaveChanges();
        }
        return rel.id_rel;
    }
    
  • 临时兜底方案:捕获异常做兼容(不推荐长期使用)
    如果不想改动现有逻辑,可以直接捕获DbUpdateConcurrencyException,确认是无变更导致的0行影响时直接返回。但这个方案会吞掉真实的并发冲突(比如记录被其他操作删除),只适合临时修复使用。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 01:36:26