无法跟踪实体类型的实例,因为已经在跟踪具有相同键值对的另一个实例 'Id'

Posted

技术标签:

【中文标题】无法跟踪实体类型的实例,因为已经在跟踪具有相同键值对的另一个实例 \'Id\'【英文标题】:The instance of the entity type cannot be tracked because another instance with the same key value pair for'Id' is already being tracked无法跟踪实体类型的实例,因为已经在跟踪具有相同键值对的另一个实例 'Id' 【发布时间】:2021-11-24 16:20:41 【问题描述】:

我知道这个问题已经被问过好几次了,大多数人都直接建议解决问题中提出的代码片段,但我真的很想知道这个错误背后的核心原因,因为我们得到这个错误的原因是什么错误 ?我在许多问题中看到人们给出不同的答案可以解决这个问题,但我仍然没有找到这个错误的确切原因。我已经读到这是由于 EF Core 的跟踪行为造成的,但是这种行为究竟是什么导致了这个问题?

我们将不胜感激一些小的代码示例。谢谢。

【问题讨论】:

最近偶然发现了github.com/dotnet/efcore/issues/12459#issuecomment-399994558。可能会有所帮助。 发生这种情况的情况可能很多。你可以复制它并在github中分享它吗?所以想帮忙的人可以看看。 【参考方案1】:

好吧,我已经调试了 EF Core 的这一部分。 ChangeTracker 中至少有两次检查实体是否存在。

    通过实体引用查找条目(如果找到) - 一切正常。 按实体的主键查找条目(如果找到) - 您有此例外。

如果第二次检查通过,附加实体会在两个字典中注册(如果您有多个键,则将通过附加字典来按键查找条目)。

这是简化的解释,但它解释了为什么最好先查看已跟踪的实体,然后决定使用哪个条目来避免此类异常。

【讨论】:

【参考方案2】:

原因本质上是在错误消息中..

当您使用 EF 从数据库下载内容时,除非您告诉它不要这样做,否则它将跟踪它下载的实体。无论您对获得的实体做什么,EF 都会记住它。这不是唯一的原因,但一个非常明显的原因是,当需要保存更改时,它会让生活变得更轻松:

var u = db.Users.FirstOrDefault(x => x.Name == "John");
u.Name = "Jim";
db.SaveChanges();

如果 EF 确实只是下载了数据,创建了一个 User 并将其移交给它并且没有保留任何内存,那么 SaveChanges 将不起作用;它必须看起来像

db.SaveChanges(u);

即您必须将更改后的数据返回。如果您想使用 乐观并发,这也会带来更多的复杂性,因为这通常通过将数据库内的值与最初下载的值进行比较来了解其他人是否在此期间编辑了数据库。你已经拥有了那个对象。

乐观的保存可能如下所示:

UPDATE user SET name = 'Jim' WHERE id = 123 and name = 'John'

我们知道用户拥有的原始名称(“John”)包含在更新查询中。如果没有其他人更改用户名,那么很好,更新将成功,因为 WHERE 子句将为真。如果有人确实在我们之前重命名了这个用户,那么更新失败(更新 0 行),我们可以处理它。

如果 EF 失去了对旧名称的所有记忆,我们就无法做到这一点;一个User 对象只有一个简单的Name 字符串属性,在我们用"Jim" 覆盖之前不记得"John"。这意味着如果EF 已经创建了一个用户,并且只是简单地将它交给你,它永远不会知道用户的原始名称是什么时候将其更新回数据库

公平地说,在 EF 上下文的背景中确实有很多智能的东西,远远超出我们所看到的(即“用户对象”)并记住它看到的对象和有关它们的数据,它们的原始值、当前值、是否添加/更新/删除它们等对于能够有效运行至关重要


问题来了,如果您使用与 EF 已知的主键相同的主键创建另一个全新的 User 实体,并尝试将其附加到 EF 的对象存储:

var u1 = db.Users.FirstOrDefault(x => x.Name == "John"); //EF now knows user 123
...
var u2 = new User  Id = 123, Name = "Mary"
db.Users.Add(u2); //EF already knows user 123; John

如果 EF 知道两个 ID 均为 123 的用户,它将无法确定哪条记录是权威记录的真实性,因此它拒绝开始记住另一个具有它已经知道的 ID 的不同用户。在上面的代码中,两个用户是内存中不同位置的不同对象,他们共享相同的 ID。这无法映射回数据库


可能还应该指出,如果您告诉 EF ID 是由数据库生成的,那么您在执行 Add 时为 ID 输入的内容根本无关紧要:EF 会知道它是一个添加的实体,它的 ID 在保存时会被数据库覆盖,因此如果您的“临时”与现有实例冲突,您将不会收到“具有相同 ID 的另一个实例”错误

【讨论】:

以上是关于无法跟踪实体类型的实例,因为已经在跟踪具有相同键值对的另一个实例 'Id'的主要内容,如果未能解决你的问题,请参考以下文章

无法跟踪实体类型的实例,因为已在跟踪具有相同键值的另一个实例

递归 linq 表达式以获取非 NULL 父值?

附加类型实体失败,因为相同类型的另一个实体已经具有相同的主键值。

在 asp.net core 中插入/更新记录时出错

ASP.NET MVC - 附加类型为“MODELNAME”的实体失败,因为同一类型的另一个实体已经具有相同的主键值

Asp.net core System.InvalidOperationException:无法跟踪实体类型x的实例