EF6迁移EF Core添加含子实体对象时报重复键错误求助
我明白你在从EF6迁移到EF Core时遇到的这个痛点——EF6的自动关系修复确实帮我们掩盖了不少外键设置的小失误,但EF Core的状态跟踪机制更严格,这既是优势也是需要适应的地方。
问题根源
EF6会在调用Add()后自动修复父子实体的外键关系,哪怕你给子实体设置了错误的外键值;但EF Core在执行Add()时,会立即检查上下文已跟踪的所有实体主键。你的场景中,itemDetail的复合主键是(ItemGID, LineId),之前添加的item1的detail1已经被上下文跟踪(主键为OneCrud_UnitTest, 1),当你给item2的子实体设置同样的ItemGID时,EF Core会认为你要添加一个已存在的实体,直接抛出重复主键异常。
解决方案
1. 手动提前修复外键值(推荐)
既然我们知道EF Core不会自动帮我们修正外键,最直接的方式就是在添加item2前,确保子实体的外键和父实体主键一致:
var item2 = new item { ItemGID = defaultPk2 }; item2.ItemID = item2.ItemGID; item2.TS = DateTime.Now; item2.itemDetail = new List<itemDetail>(); // 直接设置正确的外键值,和item2的ItemGID保持一致 var newDetail = new itemDetail { ItemGID = defaultPk2, LineId = 1, GUID = Guid.NewGuid().ToString() }; item2.itemDetail.Add(newDetail); unitOfWork.DataContext.Set<item>().Add(item2); unitOfWork.Commit();
这是最符合EF Core设计理念的做法,避免依赖自动修复,让代码逻辑更清晰。
2. 解除对旧实体的跟踪(特殊场景适用)
如果因为某些原因无法提前设置正确的外键,且你不再需要上下文跟踪之前的detail1,可以在添加item2前将旧实体从跟踪状态中移除:
// 找到已跟踪的旧detail实体 var trackedDetail = unitOfWork.DataContext.Set<itemDetail>().Find(defaultPk, 1); if (trackedDetail != null) { // 将其设置为无跟踪状态 unitOfWork.DataContext.Entry(trackedDetail).State = EntityState.Detached; } // 现在添加item2就不会触发重复键异常,EF Core会在SaveChanges时修复外键 unitOfWork.DataContext.Set<item>().Add(item2); unitOfWork.Commit();
注意:这种方式只适合你确定旧实体不会再被上下文操作的场景,否则可能导致状态不一致。
3. 配置关系增强自动修复能力
确保你的EF Core模型配置正确,让EF Core能更好地识别父子关系,提升自动修复的概率:
在DbContext的OnModelCreating方法中添加关系配置:
protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); // 配置item和itemDetail的父子关系 modelBuilder.Entity<item>() .HasMany(i => i.itemDetail) .WithOne() // 如果itemDetail有指向item的导航属性,这里填写属性名,比如d => d.Item .HasForeignKey(d => d.ItemGID) .OnDelete(DeleteBehavior.Cascade); // 显式配置itemDetail的复合主键 modelBuilder.Entity<itemDetail>() .HasKey(d => new { d.ItemGID, d.LineId }); }
正确的关系配置能让EF Core更准确地处理实体间的关联,减少异常发生。
总结
EF Core放弃了EF6中一些“隐式”的自动修复行为,转而强调显式的状态管理和数据正确性。优先选择手动设置正确的外键值,既能避免异常,也能让代码逻辑更易维护。
内容的提问来源于stack exchange,提问作者Gavin




