EF附加实体失败报错排查:同类型同主键实体重复问题
嘿,这个报错我太熟了——本质就是EF上下文的实体跟踪机制在“吃醋”,它绝对不允许同一类型、同一主键的多个实体同时被它跟踪。咱们来拆解下你的代码问题,再给你几个靠谱的解决办法:
问题根源拆解
你的代码里犯了两个关键的小错误:
- 重复加载同一主键实体:你先用
AsNoTracking()查询出包含ID=41的founds数组,遍历的时候手动把每个实体的状态设为Modified——这时候EF上下文已经开始跟踪这个ID=41的实体了。 - 错误的操作叠加:之后你又用
AsNoTracking()查询了一次ID=41的target,还想把它也加到上下文里修改状态。这时候上下文已经有一个被跟踪的ID=41的QuoteEntity了,自然就触发了“同一主键实体重复跟踪”的报错。
另外还有个小问题:你对修改操作调用了Add(),这完全没必要——Add()是给新实体用的,修改实体直接设置状态为Modified就行。
解决办法
我给你三个不同场景下的方案,你可以按需选:
方案1:合并操作,避免重复查询(最推荐)
既然你的需求是把ID=41的实体设为IsDefault=true,其他几个设为false,完全可以一次查询后直接处理,根本不需要第二次查询。代码改完是这样的:
public void test2() { var ids = new int[] { 39, 40, 41 }; // 一次查询出所有需要修改的实体 var founds = _db.DB_MODEL.AsNoTracking().Where(x => ids.Contains(x.ID)).ToArray(); foreach(var item in founds) { // 直接在循环里判断主键,设置对应的值 item.IsDefault = item.ID == 41; // 因为是AsNoTracking查的,手动设置为Modified让上下文跟踪并准备更新 _db.Entry(item).State = System.Data.Entity.EntityState.Modified; } _db.SaveChanges(); }
方案2:先检查已有跟踪实体再操作
如果业务逻辑必须分开查询,那在处理第二个实体前,先检查上下文里有没有已经跟踪的同主键实体,有的话直接改已跟踪的就行:
public void test2() { var ids = new int[] { 39, 40, 41 }; var founds = _db.DB_MODEL.AsNoTracking().Where(x => ids.Contains(x.ID)).ToArray(); foreach(var x in founds) { x.IsDefault = false; _db.Entry(x).State = System.Data.Entity.EntityState.Modified; } // 先去上下文的跟踪列表里找ID=41的实体 var trackedTarget = _db.ChangeTracker.Entries<QuoteEntity>() .FirstOrDefault(e => e.Entity.ID == 41); if(trackedTarget != null) { // 找到的话直接修改已跟踪的实体,不用再查一次 trackedTarget.Entity.IsDefault = true; } else { // 没找到再查询并设置状态 var target = _db.DB_MODEL.AsNoTracking().FirstOrDefault(x => x.ID == 41); if(target != null) { target.IsDefault = true; _db.Entry(target).State = System.Data.Entity.EntityState.Modified; } } _db.SaveChanges(); }
方案3:移除旧跟踪实体再附加新的
如果你一定要用新查询的实体,那可以先把上下文里已跟踪的同主键实体移除跟踪,再附加新的:
public void test2() { var ids = new int[] { 39, 40, 41 }; var founds = _db.DB_MODEL.AsNoTracking().Where(x => ids.Contains(x.ID)).ToArray(); foreach(var x in founds) { x.IsDefault = false; _db.Entry(x).State = System.Data.Entity.EntityState.Modified; } var target = _db.DB_MODEL.AsNoTracking().FirstOrDefault(x => x.ID == 41); if(target != null) { // 找到已跟踪的旧实体,把它从上下文的跟踪列表里移除 var existingEntry = _db.ChangeTracker.Entries<QuoteEntity>() .FirstOrDefault(e => e.Entity.ID == 41); if(existingEntry != null) { existingEntry.State = System.Data.Entity.EntityState.Detached; } // 现在可以安全地附加新实体并修改了 target.IsDefault = true; _db.Entry(target).State = System.Data.Entity.EntityState.Modified; } _db.SaveChanges(); }
额外注意事项
AsNoTracking()查出来的实体本身不受上下文跟踪,但只要你通过_db.Entry(x).State设置了状态,上下文就会开始跟踪它。- 永远不要对修改实体的操作调用
Add(),这会让EF误以为你要新增一个实体,反而可能引发主键冲突或者数据重复插入的问题。 - 尽量避免重复加载同一主键的实体到上下文,这不仅会报错,还会浪费数据库查询和内存资源。
内容的提问来源于stack exchange,提问作者ibamboo
相关产品推荐
相关产品推荐

