Tip 47 – How fix-up can make it hard to change relationships
Problem:
Take this code:
Category oldCategory = ctx.Categories
.Include("Products")
.First(c => c.Name == "Drink");
Category newCategory = new Category {Name = "Beverage"};
foreach(Product product in oldCategory.Products)
{
newCategory.Products.Add(product);
}
Ignore for a second that in this example the solution is probably just to rename the oldCategory "Beverages" - Coming up with real world samples gets harder and harder the longer you work inside the Microsoft bubble :)
The point is you are trying to move the Products from one category to another, or more abstractly we are moving entities from one collection relationship to another.
Unfortunately the above code can fail:
The underlying collection has changed?
What?
We didn’t change it!
Right?!
Wrong.
Turns out we triggered a change.
If a Product can only live in one category, assigning the product to a different category causes the EF to ‘remove’ it from the old category (i.e. from oldCategory.Products) automagically.
So adding to the new Category removes from the old Category, and you can no longer enumerate because the 'Collection was modified'.
Note: This fails when you attempt to get the second product from the oldCategory.Products collection.
Solution:
Once you understand the problem, the solution is trivial.
Enumerate the old collection before you change any relationships, using ToArray() or one of its cousins.
var products = oldCategory.Products.ToArray();
foreach(Product product in products)
{
newCategory.Products.Add(product);
}
As you can see the workaround isn't exactly rocket science.
Knowing why you need the workaround, on the other hand, is much more involved ;)
Comments
- Anonymous
December 02, 2009
How about:oldCategory.Products.ToList().ForEach(newCategory.Products.Add); - Anonymous
December 03, 2009
The comment has been removed