EF Core操作列表属性中同ID多实例实体时提示跟踪冲突错误问询
问题根源
EF Core的实体跟踪机制要求同一主键值的实体类型,在同一个上下文实例中只能有一个被跟踪的对象实例。从第三方API反序列化得到的同ID的FishKind是完全独立的内存实例,同时EF默认会把关联导航属性对应的实体自动标记为对应状态,才会触发两个报错:
- 执行
AddAsync(Lake)时,EF默认将所有关联的Fish、FishKind都标记为Added状态,尝试插入已存在的FishKind数据,触发主键冲突 - 执行
Update(Lake)时,EF尝试将所有FishKind实例标记为Modified状态,遇到同ID的不同实例时触发跟踪冲突
推荐解决方案
方案1:新增外键属性(最优,符合EF设计规范)
在Fish实体中显式添加FishKind的外键字段,仅通过外键ID关联已有类型,不需要传递FishKind导航属性,从根源避免重复实例问题:
class Fish { public int FishId; public string Name; // 新增外键字段 public int FishKindId; // 导航属性可保留,设置为可空,不需要反序列化赋值 public FishKind? Kind; }
处理API返回数据时,仅需简单遍历赋值即可:
// 反序列化得到lake对象后执行 foreach(var fish in lake.Fish) { fish.FishKindId = fish.Kind.FishKindId; fish.Kind = null; } // 直接插入Lake即可,EF只会读取外键ID,不会操作FishKind表 await db.AddAsync(lake); await db.SaveChangesAsync();
该方案完全不需要处理跟踪状态,性能最高,后续维护成本最低。
方案2:去重FishKind实例(无需修改实体结构)
如果不能修改现有实体结构,统一替换重复实例的方案实现非常简单,仅需几行代码:
var kindCache = new Dictionary<int, FishKind>(); foreach(var fish in lake.Fish) { var kindId = fish.Kind.FishKindId; if(kindCache.TryGetValue(kindId, out var existedKind)) { // 替换为同ID的同一个实例 fish.Kind = existedKind; } else { kindCache.Add(kindId, fish.Kind); // 告诉EF该FishKind是已存在的数据,不要增改 db.Entry(fish.Kind).State = EntityState.Unchanged; } } // 此时即可正常插入Lake await db.AddAsync(lake); await db.SaveChangesAsync();
该方案仅需要在插入前做一次遍历,性能开销可以忽略,不需要调整现有实体定义。
内容的提问来源于stack exchange,提问作者Fritz
相关产品推荐
相关产品推荐

