Entity Framework creating duplicate objects from list when object has composite Primary Key

asp.net-mvc-5 c# entity-framework-6 lazy-loading

Question

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.

enter image description here

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();

enter image description here

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);
    }

enter image description here

1
1
4/14/2015 7:26:05 AM

Accepted Answer

I finally got it working. It was as simple as calling the

itemToSave.Items.Clear()

method to make sure it doesn't load the old proxy objects. What a pain that was to figure out! Here is my working code. Thanks for everyone's help.

[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;
        itemToSave.Items.Clear();// This needs to be done, otherwise it will try and load the list from the DB again.                
        itemToSave.Items = (from i in viewModel.Items
                            where !string.IsNullOrEmpty(i.ClaimValue)
                            select new ClaimGroupItem
                            {
                                ClaimValue = i.ClaimValue,
                                MenuItemId = i.MenuItemId,
                            })
                .ToList();

        _repository.Update<ClaimGroup>(itemToSave);
        return RedirectToAction("Groups", new { updated = true });
    }
    return View(viewModel);
}
0
4/16/2015 1:34:39 AM

Popular Answer

If you don't need Proxies, during construction of your DBContext set ProxyCreationEnabled = false and check. I have seen cases when we don't even need Proxies and EF by default creates one for all Entities.



Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow