EF更新时主键跟踪冲突求助:SQL插入后如何处理实体跟踪?
我正在为Entity Framework代码编写测试用例,为实现逻辑隔离,通过原生SQL语句向数据库插入模型:
_context.Database.ExecuteSqlInterpolated( $@"INSERT INTO MyModel(Id, Name, PostTown) VALUES({Id}, {myModel.Name}, {myModel.PostTown});");
基于_context.Set<MyModel>().Find()编写的多个查询测试均正常运行。
现尝试编写测试验证更新方法功能,测试代码如下:
MyModel myModel = new MyModel("AName", "ATown"); _context.Database.ExecuteSqlInterpolated( $@"INSERT INTO MyModel(Id, Name, PostTown) VALUES({myModel.Id}, {myModel.Name}, {intermediary.PostTown});"); ... _context.Set<MyModel>().Update(myModel); await _context.SaveChangesAsync();
但更新操作失败,报错信息如下:
The instance of entity type 'MyModel' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values.
请问在SQL插入操作后,是否需要执行某些操作让EF正确识别该实体?
问题根源是EF的实体跟踪机制冲突:
- 用原生SQL插入数据时,EF上下文不会自动跟踪这条实体;当调用
Update(myModel)时,EF会尝试将内存中的myModel实例纳入跟踪。 - 若上下文此前已通过
Find或其他操作加载过同ID的实体,就会触发重复跟踪的报错。
针对测试场景,可采用以下几种解决方式:
方式一:插入后重新查询实体再更新
原生SQL插入完成后,从数据库重新获取该实体,修改属性后保存:
MyModel myModel = new MyModel("AName", "ATown"); _context.Database.ExecuteSqlInterpolated( $@"INSERT INTO MyModel(Id, Name, PostTown) VALUES({myModel.Id}, {myModel.Name}, {intermediary.PostTown});"); // 重新获取数据库中的实体 var existingModel = await _context.Set<MyModel>().FindAsync(myModel.Id); // 修改目标属性 existingModel.Name = "UpdatedName"; existingModel.PostTown = "UpdatedTown"; await _context.SaveChangesAsync();
方式二:手动管理实体跟踪状态
先检查上下文是否已跟踪同ID实体,若有则先分离,再将本地实例附加并标记为修改状态:
MyModel myModel = new MyModel("AName", "ATown"); _context.Database.ExecuteSqlInterpolated( $@"INSERT INTO MyModel(Id, Name, PostTown) VALUES({myModel.Id}, {myModel.Name}, {intermediary.PostTown});"); // 检查并分离已跟踪的同ID实体 var trackedEntry = _context.ChangeTracker.Entries<MyModel>() .FirstOrDefault(e => e.Entity.Id == myModel.Id); if (trackedEntry != null) { trackedEntry.State = EntityState.Detached; } // 附加本地实例并标记为已修改 _context.Attach(myModel); _context.Entry(myModel).State = EntityState.Modified; await _context.SaveChangesAsync();
方式三:统一使用EF API操作实体
测试场景下,直接用EF的Add方法插入实体,上下文会自动跟踪实例,后续更新无需额外处理:
MyModel myModel = new MyModel("AName", "ATown"); _context.Set<MyModel>().Add(myModel); await _context.SaveChangesAsync(); // 直接修改属性后保存 myModel.Name = "UpdatedName"; await _context.SaveChangesAsync();
内容的提问来源于stack exchange,提问作者Andy

