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; }); ****

它在第二次调用时爆炸,即使这是数据库中的唯一行。

关于如何解决这个问题的任何想法? 还是强制检查两个密钥?

DBContext 引发附加唯一对象(具有可为空的外键)的错误

如@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关于编程实体框架的书。

相关文章: