Entity Framework实体跟踪冲突求助:ActivityModel重复跟踪报错
控制器调用ChangeActivity方法时,传入的source和destination实例来自另一个DbContext,针对SQL Server数据库的单元测试未出现此问题。
模型定义
public class EntryModel { public int Id { get; set; } public List<ActivityModel> Activities { get; set; } ... } public class ActivityModel { public int Id { get; set; } ... }
问题代码
public ActivityChangeModel ChangeActivity(ActivityModel source, ActivityModel destination, Guid userGuid) { DeleteActivity(source); OverrideEntries(source, destination, userGuid); var change = AddChange(source, destination, userGuid); _db.SaveChanges(); return change; } private void DeleteActivity(ActivityModel source) { var model = _db.Activity.Single(x => x.Id == source.Id); model.Deleted = true; } private void OverrideEntries(ActivityModel source, ActivityModel destination, Guid userGuid) { var entries = _db.Entry.Include(x => x.Activities).Where(x => x.Activities.Contains(source)).ToList(); foreach (var entry in entries) { entry.Deleted = true; } foreach (var entry in entries) { var newEntry = new EntryModel(entry.StartTime, entry.EndTime, entry.RecordedTime, new List<ActivityModel>(), false, userGuid); _db.Entry.Add(newEntry); var activities = entry.Activities; activities.Remove(source); activities.Add(destination); newEntry.Activities = activities; } } private ActivityChangeModel AddChange(ActivityModel source, ActivityModel destination, Guid userGuid) { var change = new ActivityChangeModel(source, destination, _timeService.Current, userGuid); _db.ActivityChange.Add(change); // 抛出错误 return change; }
抛出的异常
System.InvalidOperationException: 'The instance of entity type 'ActivityModel' cannot be tracked because another instance with the key value '{Id: 5}' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.'
已尝试的解决方法
- 在添加activity到集合前调用
_db.ChangeTracker.Clear(); - 在
var activities = entry.Activities;前通过_db.ChangeTracker.DebugView.LongView查看跟踪状态,发现Id为5的ActivityModel已被跟踪且未修改
解决方案
核心原因
传入的source/destination来自外部DbContext,当前DbContext执行var model = _db.Activity.Single(x => x.Id == source.Id);时,已经跟踪了一个同Key的ActivityModel实例;后续AddChange方法中使用外部DbContext的实体实例时,EF Core尝试跟踪该实例,导致同Key实体重复跟踪冲突。
具体修复步骤
改用标识字段传递,避免跨DbContext传实体
修改ChangeActivity方法参数为实体Id,在当前DbContext内查询获取可跟踪实例:public ActivityChangeModel ChangeActivity(int sourceId, int destinationId, Guid userGuid) { var source = _db.Activity.Single(x => x.Id == sourceId); var destination = _db.Activity.Single(x => x.Id == destinationId); // 后续原有逻辑不变 }分离外部DbContext的实体实例
如果必须传递实体对象,在当前DbContext使用前将其标记为未跟踪:// 在方法开头添加 _db.Entry(source).State = EntityState.Detached; _db.Entry(destination).State = EntityState.Detached;或者在外部DbContext查询实体时使用
AsNoTracking(),确保实例未被跟踪。修改集合操作逻辑,避免直接修改跟踪集合
当前代码直接修改entry.Activities(来自当前DbContext的跟踪集合)并赋值给新Entry,易导致跟踪混乱,应创建集合副本操作:// 替换OverrideEntries中的集合操作代码 var newActivities = new List<ActivityModel>(entry.Activities); newActivities.Remove(source); newActivities.Add(destination); newEntry.Activities = newActivities;
内容的提问来源于stack exchange,提问作者Theodor349

