EF Core 2.1批量插入报错:同一主键实例已被跟踪
解决EF Core 2.1批量插入跟踪冲突问题
1. 去掉手动赋值的Id,交给EF/数据库自动生成
你数据库里Id是自动生成的newid(),完全没必要手动给Id赋值,手动生成反而会干扰EF的跟踪逻辑。改下实体创建代码:
foreach (var e in encountersArray) { items.Add(new MyItem() { // 删掉 Id = Guid.NewGuid() 这行 LearnerId = learnerId, EncounterId = e, Modified = DateTime.Now }); }
2. 直接用DbSet的AddRange,避免上下文误关联
别用context.AddRange,直接通过对应的DbSet添加集合,EF会明确只处理这些新插入的条目,不会误触其他已跟踪的实体:
using (var context = new MyContext()) { var items = new List<MyItem>(); foreach (var e in encountersArray) { items.Add(new MyItem() { LearnerId = learnerId, EncounterId = e, Modified = DateTime.Now }); } // 直接调用DbSet的AddRange context.MyItems.AddRange(items); context.SaveChanges(); }
3. 全局禁用跟踪(纯插入场景首选)
既然你只需要插入数据,不需要后续跟踪这些实体,直接把上下文的跟踪模式设为NoTracking,从根源上避免跟踪冲突:
using (var context = new MyContext()) { // 关闭上下文的实体跟踪 context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; var items = new List<MyItem>(); foreach (var e in encountersArray) { items.Add(new MyItem() { LearnerId = learnerId, EncounterId = e, Modified = DateTime.Now }); } context.MyItems.AddRange(items); context.SaveChanges(); }
4. 检查并解除已跟踪的Learner实体
如果你的代码在创建这个上下文之前,已经查询过同一个learnerId对应的Learner实体(不管是当前上下文还是其他上下文残留的跟踪),EF会在处理外键时认为有重复跟踪的实体。可以手动解除跟踪:
using (var context = new MyContext()) { // 检查本地缓存里有没有已跟踪的Learner,有的话解除跟踪 var trackedLearner = context.Learners.Local.FirstOrDefault(l => l.Id == learnerId); if (trackedLearner != null) { context.Entry(trackedLearner).State = EntityState.Detached; } var items = new List<MyItem>(); foreach (var e in encountersArray) { items.Add(new MyItem() { LearnerId = learnerId, EncounterId = e, Modified = DateTime.Now }); } context.MyItems.AddRange(items); context.SaveChanges(); }
报错原因
这个错误是因为EF上下文已经在跟踪一个带相同LearnerId的实体(大概率是之前查询加载的Learner对象),当你批量插入新的MyItem时,EF尝试关联这个已跟踪的Learner,导致跟踪冲突。上面的几个方案要么避免干扰EF的主键逻辑,要么明确操作范围,要么直接禁用跟踪,都能解决问题,而且是单次上下文批量插入,效率比循环创建上下文高得多。
内容的提问来源于stack exchange,提问作者Steve McNiven-Scott
相关产品推荐
相关产品推荐

