EF Core 更新Relation实体时提示同ID实例已被追踪如何解决
问题根因定位
你遇到的主键追踪冲突、更新操作不写入数据库的问题,核心是手动操作实体状态的逻辑和EF Core的原生变更追踪机制不兼容,具体是两个连续的逻辑漏洞:
- Detach操作根本达不到你预期的效果
你写的Context.Entry(oldRelation).State = EntityState.Detached,只会把oldRelation这个根实体本身从EF的追踪列表里移除,它关联的Person、Customer等导航实体,因为之前查询时已经被加载,仍然留在追踪列表里。后续你执行Context.Persons.Remove(oldRelation.Person)这类删除操作时,EF会顺着导航属性的反向引用链,把已经被Detach的oldRelation重新拉回追踪列表——因为EF的Remove操作会把当前实体关联的整个实体图全部附加到上下文,不管之前是不是被手动Detach过。等代码走到Context.Relations.Update(relation)时,上下文里已经存在一个被追踪的、主键为26的Relation实例(就是被拉回来的oldRelation),和你要附加的AutoMapper生成的新relation实例主键完全重复,自然抛出“同一主键存在多个追踪实例”的报错。 - Detach操作清空了EF的原始值快照,导致变更无法被识别
当你把oldRelation设为Detached状态时,EF会同步删除之前为这个实体存储的、从数据库查询出来的原始值快照。后面oldRelation被导航实体的操作重新拉回追踪时,EF会把oldRelation当时的当前值作为原始快照存储。这时候你再执行Context.Entry(oldRelation).CurrentValues.SetValues(relation),虽然内存里的oldRelation属性已经被赋值成新值,但EF对比原始快照和当前值时,识别不到任何差异,不会把实体状态标记为Modified,SaveChanges时自然不会生成对应的UPDATE语句,数据库也就收不到更新。
正确的实现方案
完全去掉手动Detach实体的冗余逻辑,顺着EF Core的原生追踪机制写代码:
- 保留仓储层查询oldRelation的流程——这个查出来的实体本身就是被EF追踪的,EF已经自动存好了从数据库返回的原始值快照
- 保留原有关联子实体的删除判断逻辑,基于被追踪的oldRelation做判断即可
- 不需要把AutoMapper生成的新relation实例手动附加到上下文,直接通过
CurrentValues.SetValues把新实例的标量属性同步到被追踪的oldRelation上,EF会自动对比快照识别变更,把需要更新的属性标记为Modified状态
修改后的Repository层参考代码:
public void UpdateRelation(Relation oldRelation, Relation relation) { if (oldRelation == null) return; // 同步标量属性值,EF自动识别变更生成更新SQL Context.Entry(oldRelation).CurrentValues.SetValues(relation); // 保留原有子实体删除逻辑,无需任何Detach操作 if (oldRelation.Person != null && relation.Person == null) { Context.Persons.Remove(oldRelation.Person); } if (oldRelation.Customer != null && relation.Customer == null) { Context.Customers.Remove(oldRelation.Customer); } if (oldRelation.Supplier != null && relation.Supplier == null) { Context.Suppliers.Remove(oldRelation.Supplier); } if (oldRelation.Employee != null && relation.Employee == null) { Context.Employees.Remove(oldRelation.Employee); } }
额外注意事项
CurrentValues.SetValues只会同步标量属性(比如Code、各类外键Id这类非导航属性),如果新的relation实例包含需要更新的导航属性值,需要手动把oldRelation的对应导航属性指向新实例,EF会自动处理导航关联的更新。- 同一个EF上下文里,绝对不能同时追踪两个同类型、同主键的实体实例,否则必然触发追踪冲突。对已存在的被追踪实体做更新时,优先直接修改被追踪实例的属性值,不要新建实例再手动附加。
内容的提问来源于stack exchange,提问作者Svenmarim
相关产品推荐
相关产品推荐

