I am sure this question has been asked before, so I apologize in advance, but am not sure of the correct keywords to include in my searches…
I am having trouble understanding the proper pattern for updating (or even inserting) an object when one of its properties is a collection of other properties in a disconnected environment (like a website). My issue has to do with the idea that a web application is only returning a collection of id’s as opposed to the full object. I think the best way to explain this is with code snippets.
Given the following objects
Public Class User
Public Property UserId As Integer
Public Property Username As String
Public Property Roles As ICollection(Of Role)
End Class
Public Class Role
Public Property RoleId As Integer
Public Property RoleName As String
Public Property Users As ICollection(OF User)
End Class
Public Class EFDbContext
Inherits Entity.DbContext
Public Property Users As Entity.DbSet(Of User)
Public Property Roles As Entity.DbSet(Of Role)
End Class
A database is created with 3 tables – Users, Roles, and RoleUsers.
I know I can easily do the following
Dim db = New EFDbContext()
Dim r1 = New Role() With { .RoleName = "User" }
Dim r2 = New Role() With { .RoleName = "Admin" }
db.Roles.Add(r1)
db.Roles.Add(r2)
Dim u1 = New User() With { .UserName = "test1", .Roles = New List(Of Role) }
u1.Roles.Add(r1)
db.Users.Add(u1)
db.SaveChanges()
And it will save both new roles to the database (giving them RoleId values of 1 and 2 respectively), a new user (giving it a UserId value of 1) and a new Role-User entry with RoleId 1 and UserId 1.
However, when dealing with a disconnected scenario like a website, most people would have a View Model to represent the input from the user which then gets mapped back to the Entities. In addition, for values representing the Roles, the data coming back would most likely only contain the unique key representing the Role. For example,
Public Class UpdatedUserViewModel
Public Property UserId As Integer
Public Property Username As String
Public Property RoleIds As ICollection(Of Integer)
End Class
...
...
Dim userEntity = db.Users.Find(user.Values.UserId)
AutoMapper.Mapper.Map(userValues, userEntity)
So while the userEntity.Roles collection may contain a single item, the mapper probably just added the entry with something like
ForMember(Function(u) u.Roles, Sub(m) m.MapFrom(Function(su) su.RoleIds.Select(Function(r) New Role() With {.RoleId = r})))
And now we come to the problem, when the SaveChanges() method is called, EF throws a Validation error because the .RoleName property is Nothing.
How does this situation get handled? Are we supposed to manually loop through the Roles and fetch each one from the database? Can we not use mapping tools? Do I give bogus values for the “missing” properties and then loop through and mark them as Unchanged?
I know this was long but I thought the walk-throughs would be helpful…
Thanks.
You can use this algorithm
Continue with child entities of above
…
Save and apply changes
If you have an entity with a non-nullable navigation that already exists in DB and has not yet been accessed via context, you can set the relationship by ID (assuming you’ve mapped the FK to a property in the model) instead of setting the entity itself.
If your IDs are not store generated, make sure you set them too. If they are, make sure they are defined as store generated in EDMX.
If you have FKs in the DB, make sure the EDMX is aware of them so that the inserts will happen in the correct order (or if using Oracle you can try using deferred constraints instead if you want).