原因本质上是在错误消息中..
当您使用 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 的另一个实例”错误