DBContext抛出错误附加唯一对象,可以使用可为空的外键

时间:2014-04-01 16:23:51

标签: c# entity-framework

ObjectStateManager周围的大多数帖子都是基于唯一主键的真实重复问题。我的问题是我的表没有主键,但它有多个外键,其中一个是Nullable。

class MyObject
{
    int   Key1;
    int?  Key2;
}


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

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

有关如何解决此问题的任何想法?或强制检查两个键?

1 个答案:

答案 0 :(得分:0)

正如@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在后台通过相关对象的导航属性进行管理。但是,如果join-table包含的属性不仅仅是相关对象的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关于编程实体框架的书籍。