EF Core:关联已存在实体时追踪冲突问题求助
问题:EF Core多对多关联时实体跟踪冲突报错
实体定义
class Action { public int Id {get;set;} public List<Case> Cases{ get; set; } = new(); } class Case { public int Id {get;set;} public string Value { get; set; } public List<Action> Actions { get; set; } = new(); }
业务场景与问题代码
收集待写入的Action实例时,先查询数据库判断关联的Case是否存在:存在则直接关联数据库中的实例,不存在则创建新Case暂存。代码如下:
var result = new List<Case>(); using (var context = new MyDBContext()) { foreach (var item in cases) { var caseFromDB = context.Cases.FirstOrDefault(x => x.Value == item); if (caseFromDB != null) { result.Add(caseFromDB); return result; } else result.Add(new Case { Value = item }); } } action.Cases.AddRange(result);
完成计算后用新上下文写入所有Action时,触发报错:
"The instance of entity type 'Case' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked...."
写入代码:
using (var context = new MyDBContext()) { foreach (var action in actions) { context.Actions.Add(action); } context.SaveChanges(); }
移除已存在的Case会丢失关联,需解决跟踪冲突问题。
解决方案
方法1:全程使用同一个数据库上下文
避免跨上下文传递实体实例,将业务逻辑与写入操作放在同一个using块中:
using (var context = new MyDBContext()) { var actions = new List<Action>(); // 处理Action与Case的关联逻辑 foreach (var actionData in actionDatas) { var action = new Action(); var caseList = new List<Case>(); foreach (var item in actionData.Cases) { var caseFromDB = context.Cases.FirstOrDefault(x => x.Value == item); if (caseFromDB != null) { caseList.Add(caseFromDB); } else { var newCase = new Case { Value = item }; caseList.Add(newCase); context.Cases.Add(newCase); } } action.Cases.AddRange(caseList); actions.Add(action); } // 批量写入Action context.Actions.AddRange(actions); context.SaveChanges(); }
方法2:将已存在的Case附加到新上下文
写入前对每个Action关联的Case做处理:已存在的Case(非默认Id)附加到新上下文并标记为未修改,新创建的Case直接添加:
using (var context = new MyDBContext()) { foreach (var action in actions) { foreach (var c in action.Cases) { if (c.Id != 0) { context.Cases.Attach(c); context.Entry(c).State = EntityState.Unchanged; } else { context.Cases.Add(c); } } context.Actions.Add(action); } context.SaveChanges(); }
方法3:查询已存在Case时使用AsNoTracking
第一次查询数据库中的Case时,禁用上下文跟踪,避免跨上下文的跟踪冲突:
var result = new List<Case>(); using (var context = new MyDBContext()) { foreach (var item in cases) { var caseFromDB = context.Cases.AsNoTracking().FirstOrDefault(x => x.Value == item); if (caseFromDB != null) { result.Add(caseFromDB); } else { result.Add(new Case { Value = item }); } } } action.Cases.AddRange(result);
写入时EF Core会自动识别:有Id的Case关联已有记录,新Case直接插入。
方法4:关联已存在Case的Id(多对多场景推荐)
不传递完整Case实例,仅创建包含Id的" stub "实例保留关联,不会触发跟踪冲突:
var result = new List<Case>(); using (var context = new MyDBContext()) { foreach (var item in cases) { var caseFromDB = context.Cases.FirstOrDefault(x => x.Value == item); if (caseFromDB != null) { result.Add(new Case { Id = caseFromDB.Id }); } else { result.Add(new Case { Value = item }); } } } action.Cases.AddRange(result);
写入时EF Core会为有Id的Case创建关联关系,新Case则正常插入并建立关联。
内容的提问来源于stack exchange,提问作者Nikolas
相关产品推荐
相关产品推荐

