I have an object with a child collection as such:
public class ClaimGroup : BaseModel
{
public int Id { get; set; }
[Required,StringLength(100)]
public string Name { get; set; }
public int Order { get; set; }
public bool Include { get; set; }
public virtual ICollection<ClaimGroupItem> Items { get; set; }
}
The ClaimGroupItem is:
public class ClaimGroupItem : BaseModel
{
[Key,Column(Order = 0),DatabaseGenerated(DatabaseGeneratedOption.None)]
public int ClaimGroupId { get; set; }
[ForeignKey("ClaimGroupId")]
public virtual ClaimGroup ClaimGroup { get; set; }
[Key,Column(Order = 1),DatabaseGenerated(DatabaseGeneratedOption.None)]
public int MenuItemId { get; set; }
[ForeignKey("MenuItemId")]
public virtual MenuItem MenuItem { get; set; }
public string ClaimValue { get; set; }
}
As you can see, it has a composite primary key: MenuItemId and ClaimGroupId.
On updating, it creates duplicates of the ClaimGroupItem objects, with POCO objects being set correctly, but then it creates dynamic proxy items with the same values, hence duplicating the objects.
E.g.:
var items = viewModel.Items.Where(c => !string.IsNullOrEmpty(c.ClaimValue));
The items collection above containts 10 ClaimGroupItemViewModel objects as shown in the image below.
However, when I map the viewModel objects to the Model objects, the collection then has 20 objects, 10 of which are proxy items as seen below:
itemToSave.Items = (from i in items
select new ClaimGroupItem
{
ClaimValue = i.ClaimValue,
MenuItemId = i.MenuItemId,
})
.ToList();
Then when the object goes to save, I get the following error:
_repository.Update<ClaimGroup>(itemToSave);
Violation of PRIMARY KEY constraint 'PK_dbo.ClaimGroupItems'. Cannot insert duplicate key in object 'dbo.ClaimGroupItems'. The duplicate key value is (20, 6). The statement has been terminated.
The error makes sense, EF is trying to save 10 duplicate objects. Why is Entity Framework creating 10 new objects and hence duplicates?
Here is the code on POST that gets the whole list of 79 items in viewModel.Items, then we select just the ones with claimvalue not null. There are NO duplicates at this stage.
[HttpPost, ValidateAntiForgeryToken]
public ActionResult Group([Bind(Include = "Id,Name,Order,Include,Items")] ClaimGroupViewModel viewModel)
{
if (ModelState.IsValid)
{
ClaimGroup itemToSave = _repository.Get<ClaimGroup>(viewModel.Id);
itemToSave.Include = viewModel.Include;
itemToSave.Name = viewModel.Name;
itemToSave.Order = viewModel.Order;
var items = viewModel.Items.Where(c => !string.IsNullOrEmpty(c.ClaimValue));
// There is just 10 items in items variable at this point
itemToSave.Items = (from i in items
select new ClaimGroupItem
{
ClaimValue = i.ClaimValue,
MenuItem = new MenuItem { Id = i.MenuItemId}
})
.ToList();
_repository.Update<ClaimGroup>(itemToSave);
return RedirectToAction("Groups", new { updated = true });
}
return View(viewModel);
}