【问题标题】:Associations strange behavior协会奇怪的行为
【发布时间】:2013-03-29 14:41:41
【问题描述】:

我发现以下有关在 .NET 4.0 下运行的 EF5 的问题,采用 Model First 方法: 我有以下自动生成的实体:

Partial Public Class Notice

    Private _id As Integer
    Public Property id As Integer
        Get
            Return _id
        End Get
        Friend Set(ByVal value As Integer)
            _id = value
        End Set
    End Property
    Public Property order_id As Integer
    Public Property employee_id As Integer
    Public Property sysdate As Datetime
    Public Property content As String

    Public Overridable Property order As Order
    Public Overridable Property employee As Employee

End Class

Notice 实体与 Order 实体和 Employee 实体通过 1 (Order, Employee) 对多 (Notice) 的关系关联。

之后,Order 实体也与 Employee 实体有关联:many (Order) to 1 (Employee) 关系。

然后,我预计以下单元测试会失败,因为 Notice 实体关系与 Employee 实体违规(我没有分配 notice1.employee 导航属性):

<TestMethod()>
    <ExpectedException(GetType(DbUpdateException))>
    Public Sub ShouldNotAllowSaveNoticeWithoutAssignedEmployee()

        Dim notice1 = CreateNewNotice() ' returned entity has not set any relation
        notice1.order= CreateNewOrderWithAllRequiredAndRelatedEntities()

        DbContext.noticeSet.Add(notice1)
        DbContext.SaveChanges()

    End Sub

但结果测试通过了。在数据库中,Notice->employee_id 值等于 Order->employee_id 值,这是意料之外的,因为这些外键可能指向不同的 Employee 对象。我希望我必须自己设置 notice1.employee 导航属性,如果我忘记设置,我想得到DbUpdateException 异常。

这种奇怪的 EF5 行为的原因是什么?

更新:

CreateNewNotice()CreateNewOrderWithAllRequiredAndRelatedEntities() 实现:

 Protected Function CreateNewNotice(Optional ByVal suffix As Int32 = 1) As Notice

        Dim notice1 = New Notice() With {
            .sysdate = DateTime.Now,
            .content = Description & suffix ' Description is constant, for testing purposes
        }

        Return notice1 

    End Function

Protected Function CreateNewOrderWithAllRequiredAndRelatedEntities(Optional ByVal suffix As Int32 = 1) As Order

        Dim order1 = CreateNewOrder(suffix) ' returned entity has not set any relation
        order1.employee = CreateNewEmployee(suffix)
        order1.customer = CreateNewCustomerWithAllRequiredRelatedEntities(suffix)
        order1.seller = CreateNewSeller(suffix)

        For i As Integer = 1 To 3
            order1.notices.Add(CreateNewNotice(i)) ' created notices have not initialized relations
        Next

        Return order1

    End Function

【问题讨论】:

  • 贴出CreateNewNotice()CreateNewOrderWithAllRequiredAndRelatedEntities()的实现
  • 我已经发布了这些实现。对我来说也很奇怪的是,在CreateNewOrderWithAllRequiredAndRelatedEntities() 方法中创建了三个没有设置关系的通知实体,但是这些通知保存在数据库中,并设置了外键。
  • 如果不访问所有代码,很难说发生了什么。一个建议是为每个测试创建一个 DB 上下文对象,并在需要时将其传递给您的辅助函数(即以程序方式编写代码,而不是以面向对象的方式编写代码,这对于单元测试方法来说很好)
  • 测试类中有DbContext受保护的属性,在&lt;TestInitialize()&gt;属性方法中为每个测试重新实例化,然后在&lt;TestCleanup()&gt;属性方法中处理。

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


【解决方案1】:

我已经认识到这个问题,并且看起来 EF5 的行为不同,具体取决于我们使用的是“外键关联”还是“独立关联”(有关 EF 中关系的一些信息:Relationships and Navigation Properties

假设我们使用 EF5 模型优先方法(使用 .NET 4.5)。让我们考虑自动生成的实体类,与主题问题中显示的示例相关:

Partial Public Class Employee
    Public Property id As Integer
    Public Property firstname As String
    Public Property lastname As String

    Public Overridable Property Notices As ICollection(Of Notice) = New HashSet(Of Notice)
    Public Overridable Property Orders As ICollection(Of Order) = New HashSet(Of Order)

End Class

Partial Public Class Order
    Public Property id As Integer
    Public Property Employee_id As Integer
    Public Property article As String

    Public Overridable Property Notices As ICollection(Of Notice) = New HashSet(Of Notice)
    Public Overridable Property Employee As Employee

End Class

Partial Public Class Notice
    Public Property id As Integer
    Public Property Employee_id As Integer
    Public Property Order_id As Integer
    Public Property sysdate As Date
    Public Property content As String

    Public Overridable Property Employee As Employee
    Public Overridable Property Order As Order

End Class

此实体类使用“外键关联”和“独立关联”。 然后,我们有以下单元测试类:

<TestClass()>
Public Class UnitTests

    Protected DbContext As DbModelContext

    <TestInitialize()>
    Public Sub TestInitialize()

        Thread.CurrentThread.CurrentUICulture = New CultureInfo("en-us")
        DbContext = New DbModelContext()

    End Sub

    <TestCleanup()>
    Public Sub TestCleanup()

        DbContext.Dispose()

    End Sub

    <TestMethod()>
    Public Sub Test1()

        Dim notice1 = CreateNewNotice()
        notice1.Order = CreateNewOrderWithAllRequiredAndRelatedEntities()

        DbContext.NoticeSet.Add(notice1)
        DbContext.SaveChanges() ' no DbUpdateException exception

        Assert.AreEqual(notice1.Employee.id, notice1.Order.Employee.id)
        Assert.AreEqual(notice1.Employee.id, notice1.Order.Notices.First().Employee.id)

    End Sub

    <TestMethod()>
    Public Sub Test2()

        Dim notice1 = CreateNewNotice()
        notice1.Order = CreateNewOrderWithAllRequiredAndRelatedEntities()

        DbContext.NoticeSet.Add(notice1)

        Dim employee2 = CreateNewEmployee()

        DbContext.EmployeeSet.Add(employee2)

        DbContext.SaveChanges() 'DbUpdateException exception

    End Sub

    ''' <summary>
    ''' Create new Order object along with required associated objects,
    ''' however related Notice objects do not have assigned required associated objects
    ''' </summary>
    Protected Function CreateNewOrderWithAllRequiredAndRelatedEntities(Optional ByVal suffix As Int32 = 1) As Order

        Dim order1 = CreateNewOrder(suffix)
        order1.Employee = CreateNewEmployee(suffix)

        For i = suffix To suffix + 2

            order1.Notices.Add(CreateNewNotice(i))

        Next

        Return order1

    End Function

    ''' <summary>
    ''' Create new Order object without required associated objects
    ''' </summary>
    Protected Function CreateNewOrder(Optional ByVal suffix As Int32 = 1) As Order

        Dim order1 = New Order() With {
                .article = "article" & suffix
            }

        Return order1

    End Function

    ''' <summary>
    ''' Create new Employee object required without associated objects
    ''' </summary>
    Protected Function CreateNewEmployee(Optional ByVal suffix As Int32 = 1) As Employee

        Dim employee1 = New Employee() With {
                .firstname = "firstname" & suffix,
                .lastname = "lastname" & suffix
            }

        Return employee1

    End Function

    ''' <summary>
    ''' Create new Notice object without associated objects
    ''' </summary>
    Protected Function CreateNewNotice(Optional ByVal suffix As Int32 = 1) As Notice

        Dim notice1 = New Notice() With {
            .sysdate = DateTime.Now,
            .content = "Description" & suffix
        }

        Return notice1

    End Function

End Class

如果我们运行测试,Test1() 通过,但Test2() 失败并出现异常:

System.Data.Entity.Infrastructure.DbUpdateException:无法确定“Model1.NoticeEmployee”关系的主体端。多个添加的实体可能具有相同的主键。 ---> System.Data.UpdateException:无法确定“Model1.NoticeEmployee”关系的主体端。多个添加的实体可能具有相同的主键。

结论:

Test1() 内部只有一个Employee 对象,而DbContext 中只有一个Order 对象。未在代码中设置的关系(Notice.EmployeeNotice.Order)由 EF 在DbContext.SaveChanges() 语句中自动设置。在Test2()内部DbContext中有两个Employee对象,所以Notice.Employee没有自动赋值。

奇怪的是,如果我们从模型实体中删除外键属性,以便仅具有“独立关联”功能,则两个测试都会失败并出现相同的 System.Data.Entity.Infrastructure.DbUpdateException例外。

同样,如果我们从模型实体中删除导航属性,以便仅具有“外键关联”功能,则两个测试都会失败并出现相同的异常。

【讨论】:

  • +1 干得好!我一直盯着这个看几次,但没有任何线索。我担心这是一个错误。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-06-27
  • 2014-03-03
  • 2017-08-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多