Entity Framework duplicating objects in context after re-fetching

asp.net-core entity-framework-6

Question

I have noticed entity framework duplicating child objects in my in-memory context, using .NetCore 1.1 and EF 6.1.3. Can someone explain this behavior?

For example, say I have the following data model:

public class Customer 
{
    public string Name { get; set; }
    public string SomeDataINeed { get; set; }
    public int CustomerId { get; set; }
    public virtual List<Order> Orders { get; set; }
}

public class Order
{
    public string Description { get; set; }
    public double BundledDiscount {get; set; }
    public int OrderId { get; set; }
    public int CustomerId { get; set; }
    public virtual Customer Cust { get; set; }
    public virtual List<LineItem> LineItems { get; set; }
}

public class LineItem
{
    public int OrderId { get; set; }
    public int LineItemId { get; set; }
    public double LineCost { get; set; }
    public virtual Order ParentOrder { get; set; }
}

And then I have had an API endpoint where I put an Order (with its LineItems also in the body of the put) and then fetch the Customer out of the database. I have to fetch the full customer because I need to decorate it with some non-persistent data I'm grabbing from another API.

When I fetch the Customer, the LineItems are duplicated inside the context. E.g., if I had two LineItems in the original put, I will now have 4, with duplicate primary keys.

[HttpPut]
public async Task<IActionResult> PutOrder([FromBody] Order order)
{
    var customerId = order.CustomerId

    _context.Entry(order).State = EntityState.Modified;
    _context.SaveChanges();

    // now I need to fetch the full customer (just trust me, I need to fetch it).  
    // After the below call, any Line Items get duplicated inside the context, 
    // even though they are set up with primary keys.  
    // For some reason, EF does not use the primary keys to know that some 
    // line items were already in memory.
    var customer = _context.Customer
        .Include(x => x.Orders)
        .ThenInclude(y => y.LineItems)
        .Where(c => c.CustomerId == customerId)
        .FirstOrDefault()

    return Ok(customer);
}

The only solution I have found so far is to detach the original object from the context first...but it seems like I should not have to do this. It seems like Entity Framework should be able to de-duplicate automatically based on Primary and Foreign Keys.

Here's another illustration of what's happening. Context after first EF operation:

context: {
   Order: {
       id: 1,
       LineItems: [{id: 33}] // I'm not trying to affect the state here.  LineItems are only here because they are in the body of the put
   }
}

Context after the 2nd EF operation:

context: {
    Customer: {
        Order: {
            id: 1,
            LineItems: [
                {id: 33},
                {id: 33} // The line item is duplicated here
                ] 
        }
    }
}

Thanks!

1
1
10/17/2017 6:30:38 PM

Accepted Answer

It's duplicating them because, as @Steve Greene said, when you set the state of the parent entity the children entities are not modified. As far as the context is concerned those line items in your PUT are new entities, event though they have primary keys.

You have to go through every child item and attach it to the context. By doing this it should set them to Unchanged state if they have primary keys or to Added if they don't.

1
10/17/2017 6:36:33 PM


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