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
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.