c# - DBContext使用可空的外键附加唯一对象时引发错误

标签 c# entity-framework

大多数围绕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; });


这导致上下文中添加一个新的MyObject实例,该实例的主键值为100并且其Key2属性为null

接下来,您尝试将另一个项目附加到上下文:

context.MyTable.Attach(new MyObject() { Key1 = 100; Key2 = 2000; });


这样做是尝试将新项目添加到主键为100的上下文中,但失败。这是因为您已经有一个上下文由其主键值为100的对象跟踪(由上面的第一条语句执行)。

由于您需要允许null属性的Key2值,因此您不能使用复合主键,如前所述。因此,您将需要遵循@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)的书。

关于c# - DBContext使用可空的外键附加唯一对象时引发错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22791497/

相关文章:

c# - 从 Entity_id 到 NHibernate 中的实体实例

c# - 文件浏览器对话框实现

asp.net - WebAPI 中的 DTO 和投影

.net - 查找另一个地理点范围内的地理点 - SQL Server

entity-framework - 为什么使用TPC继承时Select查询使用左外连接?

asp.net - 更改 MVC 帐户数据库中的 DefaultConnection

entity-framework - Entity Framework ObjectContext 是否正确实现了工作单元模式?

c# - 如何连接字典中的值?

c# - 当只有图像名称保存在数据库中时,如何在 RDLC 报告中显示图像?

c# - 将空条目添加到绑定(bind)到实体列表的组合框