I have the two entities, Arena and Regulator, which have a many to many relationship between them. I have implemented what seems to be the EF code first accepted solution (
I have the same issue with EF not managing Many to Many relations. I disagree the solution provided by Mystere Man. I use RefactorThis.GraphDiff for all the collection updates using EF Code First. But it is not fair to say it is incorrect.
Like GilShalit has mentioned, nhibernate handles it brillinatly. It makes me fell, EF is not fully developed product. It is not our job to fetch all collection and check add/delete all these stuff. It is looks very nasty. while the collections are added, EF should handle which one to add and which one to remove. Thankfully RefactorThis.GraphDiff is helping for the time being. I prefer nhibernate not ef. Unfortunately I need to use EF in the current project.
The problem here is not that EF doesn't update relationships, it's that it doesn't automatically do so.
What you need to do is retrieve the current Arenas for the Regulator, then walk the list and delete any entries that exist that are not in your new list. Then you need to add any entries that do not already exist. Then SaveChanges.
This does work, but your problem is that you're trying to either blindly add relationships that may already exist, or trying to update ones that don't. You have to actually get the existing list and figure out which relationships to either add or delete.
I understand you've already found a solution that works for you, my point here is just that if you're trying to do a strict EF based solution, then you were going at it the wrong way.
I suppose another option would be to delete all the relationships, SaveChanges, then add the new set and SaveChanges again. This would delete some that already exist if there is overlap, but would be fairly straight forward and simple.
Another option is to Delete the existing, re-add them, then walk the original set and change the state of any that previously existed to Modified or None. A little more involved, but would only require on save.
After a lot of research I have understood that EF cannot update relationships - very surprising and disappointing. Aparently, the suggested solution is a manual update of the join table and navigation properties - not very good. NHibernate apparently does do this out of the box and I fully intend to investigate next time I need this.
Luckily I came across a truly great solution from Refactor(This) that adds an extension method to DbContext, allowing automated updating of complex relationships. There is even a Nuget package!
So here is my full solution:
I've added an integer list to the Regulator class, which gets the IDs of the selected Arenas.
Public Class Regulator
Public Property Id As Integer
Public Property Name As String
Public Property ArenaIDs() As ICollection(Of Integer)
Public Overridable Property Arenas() As ICollection(Of Arena)
End Class
In the GET Edit action, this is taken care of and a MultiSelectList is created:
' GET: /Regulator/Edit/5
Function Edit(Optional ByVal id As Integer = Nothing) As ActionResult
Dim regulator As Regulator = db.Regulators.Find(id)
If IsNothing(regulator) Then
Return HttpNotFound()
End If
For Each a In regulator.Arenas
regulator.ArenaIDs.Add(a.Id)
Next
ViewBag.MultiSelectArenas = New MultiSelectList(db.Arenas.ToList(), "Id", "Name", regulator.ArenaIDs)
Return View(regulator)
End Function
And the MultiSelectList is used in the View:
<div class="editor-field">
@Html.ListBoxFor(Function(m) m.ArenaIDs, ViewBag.MultiSelectArenas)
@Html.ValidationMessageFor(Function(model) model.Arenas)
</div>
In the POST Edit action the selection IDs are retrieved and used to update the Arenas collection. Then the magic comes in the with the UpdateGraph extension method that does what EF can not and updates the relationship!
' POST: /Regulator/Edit/5
<HttpPost()> _
<ValidateAntiForgeryToken()> _
Function Edit(ByVal regulator As Regulator) As ActionResult
If ModelState.IsValid Then
For Each i In regulator.ArenaIDs
regulator.Arenas.Add(db.Arenas.Find(i))
Next
db.UpdateGraph(Of Regulator)(regulator, Function(map) map.AssociatedCollection(Function(r) r.Arenas))
db.SaveChanges()
Return RedirectToAction("Index")
End If