DBContext 引发附加唯一对象(具有可为空的外键)的错误
本文关键字:错误 唯一 对象 DBContext | 更新日期: 2023-09-27 17:55:48
大多数关于ObjectStateManager的帖子都是基于唯一主键的真重复问题。 我的问题是我的表没有主键,但它确实有多个外键,其中一个是可为空的。
class MyObject
{
int Key1;
int? Key2;
}
context.MyTable.Attach(new MyObject() { Key1 = 100; Key2 = null; });
context.MyTable.Attach(new MyObject() { Key1 = 100; Key2 = 2000; }); ****
它在第二次调用时爆炸,即使这是数据库中的唯一行。
关于如何解决这个问题的任何想法? 还是强制检查两个密钥?
如@BenAaronson所述,在这种情况下,表中应该有一个代理项主键。实体框架根本无法处理没有定义主键的实体——事实上,我很惊讶你的代码竟然编译/运行了。也许具有实际类和属性名称的实际代码导致 EF 使用其默认约定推断主键。例如:
public class MyClass
{
public int MyClassId { get; set; }
public int MyOtherClassId { get; set; }
}
在上面的代码中,即使没有显式声明它,EF 也会假定 MyClassId
属性是类MyClass
的主键,即使这可能不是您的意图。
如果 EF 无法推断主键,并且未显式提供主键,则代码将无法编译(或最多不会运行)。
因此,查看您的代码,似乎正在发生的事情是 EF 以某种方式推断出主键(在上面的示例中,Key1
)。然后,您尝试将新对象附加到上下文:
context.MyTable.Attach(new MyObject() { Key1 = 100; Key2 = null; });
这会导致上下文添加一个主键值为 100
且其Key2
属性为 null
的新MyObject
实例。
接下来,您尝试将另一个项目附加到上下文:
context.MyTable.Attach(new MyObject() { Key1 = 100; Key2 = 2000; });
这样做是尝试将一个新项目添加到主键为 100
的上下文中,但这失败了。这是因为您已经有一个对象正在被主键值100
(由上面的第一条语句执行)的上下文跟踪。
由于需要允许 Key2
属性可能null
值,因此不能使用复合主键,如前所述。因此,您需要遵循@BenAaronson的建议并添加代理主键:
public class Object
{
// Alternatively, you can use a mapping class to define the primary key
// I just wanted to make the example clear that this is the
// surrogate primary key property.
[Key]
private int ObjectID { get; set; } // IIRC, you can make this private...
public int Key1 { get; set; }
public int Key2 { get; set; }
}
现在,您可以执行以下操作:
context.MyTable.Add(new MyObject() { Key1 = 100, Key2 = null; });
context.MyTable.Add(new MyObject() { Key1 = 100, Key2 = 2000; });
请注意,我使用了 Add
方法,而不是 Attach
。这是因为在使用 Attach
时,上下文假设您要将一个对象添加到数据库中已存在的上下文中,但该对象未通过查询引入上下文;相反,您在内存中具有它的表示形式,此时,您希望上下文开始跟踪对其所做的更改,并在调用 context.SaveChanges()
时更新数据库中的对象。使用 Attach
属性时,上下文将添加处于Unmodified
状态的对象。这不是我们想要的。我们将全新的对象添加到上下文中。所以我们使用Add
.这会告知上下文添加处于Added
状态的项目。您可以对其进行所需的任何更改。由于它是一个新项目,因此它将处于Added
状态,直到您调用 context.SaveChanges()
并且该项目将保存到数据存储中,此时,它的状态将更新为 Unmodified
。
在这一点上还有一件事需要注意。如果这是"多对多"表,则永远不需要在 EF 中手动向这种类型的联接表添加行(此语句有一些注意事项,请参见下文)。相反,您应该在关系为多对多的两个对象之间设置映射。也可以指定可选的多对多关系。如果第一个对象与第二个对象没有关系,则第一个对象的连接表中不应有行,反之亦然。
关于上面提到的联接表注意事项:如果您的联接表(即多对多映射表)很简单(这意味着表中唯一的列是将一个 ID 映射到相关 ID 的列),那么您甚至不会看到联接表作为对象模型的一部分。此表由 EF 在后台通过相关对象的导航属性进行管理。但是,如果联接表包含的属性不仅仅是相关对象的 ID 属性(这意味着您有一个现有数据库或以这种方式显式构造对象模型),那么您将有一个中间实体引用。例如:
public class A
{
public int ID { get; set; }
}
public class B
{
public int ID { get; set; }
}
public class AToB
{
// Composite primary key
[Key]
public int IdA { get; set; }
[Key]
public int IdB { get; set; }
public A SideA { get; set; }
public B SideB { get; set; }
// An additional property in the many-to-many join table
public DateTime Created { get; set; }
}
你还需要一些映射来告诉 EF 如何连接外键关系。然后,您将在对象模型中得到以下内容:
myA.AToB.SideB // Accesses the related B item to this A item.
myA.AToB.Created // Accesses the created property of AToB, telling you
// when the relationship between A and B was created.
事实上,如果您有非平凡的联接表(如此示例),则在从现有数据库生成其模型时,EF 将始终将它们包含在对象模型中。
我强烈建议你看看Julie Lerman和Rowan Miller关于编程实体框架的书。