【问题标题】:EF 5.0 - Can't make bi-directional relationship work with Code FirstEF 5.0 - 无法使用 Code First 建立双向关系
【发布时间】:2012-10-25 18:56:58
【问题描述】:

我有一个名为 WidgetCollection 的类。它有一个 Items 属性公开一个 List(Of Widget) 和一个 SelectedWidget 属性。我希望 EF 构建数据库如下:

  • 在我的 Widgets 表中添加一个 WidgetCollection_Id 属性,指定 每个小部件在哪个 WidgetCollection 中
  • 在我的 WidgetCollection 表中添加一个 SelectedWidget_Id 属性,指定哪个 已选择小部件
  • 添加从 WidgetCollection.Id 到 Widget.WidgetCollection_Id 的一对多关系
  • 添加从 Widget.Id 到 WidgetCollection.SelectedWidget_Id 的 1 对 0 或 1 关系

我可以确认它确实正确地构建了数据库架构,但是如果我在分配给 SelectedWidget 后保存了上下文,则会收到以下错误:

发生 System.Data.Entity.Infrastructure.DbUpdateException H结果=-2146233087 消息=保存不为其关系公开外键 > 属性的实体时发生错误。 EntityEntries 属性将返回 null,因为无法将单个实体标识为异常源。通过在实体类型中公开外键属性,可以更轻松地在保存时处理异常。

内部例外

无法确定相关操作的有效顺序。由于外键约束、模型要求或存储生成的值,可能存在依赖关系。

我可以通过从不分配 WidgetCollect.SelectedWidget 来防止此错误。

我想问题在于 EF 无法弄清楚如何处理两个方向的关系,但我就是找不到将其指向正确方向的方法。示例代码如下,欢迎所有建议!

Public Class Widget
    Private miId As Integer
    Public Property Id As Integer
        Get
            Return miId
        End Get
        Set(value As Integer)
            miId = value
        End Set
    End Property

    Private msName As String
    Public Property Name As String
        Get
            Return msName
        End Get
        Set(value As String)
            msName = value
        End Set
    End Property
End Class
Public Class WidgetCollection
    Private miId As Integer
    Public Property Id As Integer
        Get
            Return miId
        End Get
        Set(value As Integer)
            miId = value
        End Set
    End Property


    Private msName As String
    Public Property Name As String
        Get
            Return msName
        End Get
        Set(value As String)
            msName = value
        End Set
    End Property

    Private moSelectedWidget
    Public Property SelectedWidget As Widget
        Get
            Return moSelectedWidget
        End Get
        Set(value As Widget)
            moSelectedWidget = value
        End Set
    End Property

    Private moWidgets As New List(Of Widget)
    Public Property Widgets As List(Of Widget)
        Get
            Return moWidgets
        End Get
        Set(value As List(Of Widget))
            moWidgets = value
        End Set
    End Property

End Class

Public Class MyContext
    Inherits DbContext
    Public Property Widgets As DbSet(Of Widget)
    Public Property WidgetCollections As DbSet(Of WidgetCollection)
End Class

Class Application
    Public Sub New()
        Database.DefaultConnectionFactory = New SqlCeConnectionFactory("System.Data.SqlServerCe.4.0", "", "Data Source=\EFtest.sdf")
        Database.SetInitializer(New DropCreateDatabaseIfModelChanges(Of MyContext))
        Dim oContext = New MyContext

        Dim oWidgetA = New Widget With {.Name = "Widget A"}
        Dim oWidgetB = New Widget With {.Name = "Widget A"}
        Dim oWidgetCollection = New WidgetCollection With {.Name = "My widget collection"}
        oWidgetCollection.Widgets.Add(oWidgetA)
        oWidgetCollection.Widgets.Add(oWidgetB)
        oWidgetCollection.SelectedWidget = oWidgetA  'Removing this line prevents error

        oContext.WidgetCollections.Add(oWidgetCollection)
        oContext.SaveChanges()
    End Sub
End Class

【问题讨论】:

    标签: vb.net entity-framework ef-code-first entity-framework-5


    【解决方案1】:

    我认为异常意味着它所说的:

    无法确定相关操作的有效顺序。

    这两行...

    oWidgetCollection.Widgets.Add(oWidgetA)
    oWidgetCollection.SelectedWidget = oWidgetA 
    

    ...表示EF必须先存储oWidgetCollection,然后才能在oWidgetA中设置WidgetCollection_Id外键,但是第二行要求以相反的方式存储对象,即oWidgetA必须在 EF 可以在 oWidgetCollection 中设置外键 SelectedWidget_Id 之前存储。

    要解决冲突,我认为您必须保存更改两次:

    oWidgetCollection.Widgets.Add(oWidgetA)
    oWidgetCollection.Widgets.Add(oWidgetB)
    
    oContext.WidgetCollections.Add(oWidgetCollection)
    oContext.SaveChanges()
    
    oWidgetCollection.SelectedWidget = oWidgetA
    oContext.SaveChanges()
    

    顺便说一句:这个期待……

    添加从 Widget.Id 到 WidgetCollection.SelectedWidget_Id 的 1 对 0 或 1 关系

    ...不正确。 EF 将创建另一个一对多关系,即可以为多个WidgetCollections 选择相同的SelectedWidget。当您仅在关系的一侧具有导航属性时,默认关系 EF 将按照约定创建,始终是一对多的。您需要数据注释或 Fluent API 来覆盖此默认行为。

    我建议将此关系保留为一对多。一对一关系更加困难,EF 仅支持与共享主键的一对一关系,这意味着您无法选择不同的小部件作为选定的。唯一可能选择的小部件是具有与 WidgetCollection 相同的主键值的小部件。

    【讨论】:

    • 虽然我在我的原始项目中尝试过,但我没有在我的示例项目中尝试过,所以错误地假设我正在重现的错误具有相同的原因。事实证明,我最初项目中的真正问题在于我的收藏 ID 中的属性获取器。一个愚蠢的问题,是的,但也是一个违反直觉的错误信息。感谢您朝着正确的方向前进!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多