Domanda

I found a following issue regarding EF5 running under .NET 4.0, in Model First approach: I have following auto-generated entity:

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 entity is associated with Order entity and Employee entity by 1 (Order, Employee) to many (Notice) relationship.

Afterward, Order entity has also association with Employee entity: many (Order) to 1 (Employee) relationship.

Then, I expect the following unit test to fail, because of Notice entity relation to Employee entity violation (I don't assign notice1.employee navigation property):

<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

But in result test is passed. In Database, Notice->employee_id value is equal to Order->employee_id value, what is not expected, because these foreign keys may point to different Employee object. I expected that i have to set up notice1.employee navigation property myself, and I would like to get DbUpdateException exception if i forget to do it.

What is the reason of this strange EF5 behavior?

Update:

CreateNewNotice() and CreateNewOrderWithAllRequiredAndRelatedEntities() implementation:

 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
È stato utile?

Soluzione

I have recognized this issue, and it looks like there is a different behavior of EF5 depending on whether we use 'foreign key associations' or 'independent associations' (some informations about relationships in EF: Relationships and Navigation Properties)

Lets assume that we use EF5 Model First Approach (with .NET 4.5). Lets take in consideration automatically generated entity classes, related to example showed up in topic's questions:

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

This entities classes use 'foreign key associations' and 'independent associations'. Then, we have the following unit tests 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

If we run tests, Test1() passes, but Test2() fails with exception:

System.Data.Entity.Infrastructure.DbUpdateException: Unable to determine the principal end of the 'Model1.NoticeEmployee' relationship. Multiple added entities may have the same primary key. ---> System.Data.UpdateException: Unable to determine the principal end of the 'Model1.NoticeEmployee' relationship. Multiple added entities may have the same primary key.

Conclusions:

Inside Test1() there is only one Employee object and one Order object in the DbContext. Relations which are not set in code (Notice.Employee, Notice.Order) are automatically set up by EF within DbContext.SaveChanges() statement. Inside Test2() there are two Employee object in DbContext, so Notice.Employee has not automatically assigned value.

What is strange, if we remove foreign key properties from model entities, in order to have only 'independent associations' functionality, both tests fails with the same System.Data.Entity.Infrastructure.DbUpdateException exception.

Similarly, if we remove navigation properties from model entities, in order to have only 'foreign key associations' functionality, both tests fails with the same exception.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top