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
相关产品推荐
相关产品推荐

