5

I'm using entity framework code first and exposing the northwind database through a WCF REST HTTP interface.

I've not exposed the OrderDetails table (order items) as it doesn't make sense creating an order and then adding each required OrderDetail seperately through another service. To my mind it needs to be an atomic transaction that either succeeds or fails as one. Therefore I include the Order.OrderDetails collection when passing to the client and assume I'm going to get one when an order is created or updated.

The problem however seems to be detecting changes to the OrderDetails collection when reattaching the Order entity for an update. The order itself can be set as modified to update those properties but this doesn't cascade to the OrderDetail items. So I can manually go through and set updated ones to modified but the problem lies in figuring out which ones are updated in the first place. Setting a new OrderDetail to modified will cause an error when trying to save.

I read a recommendation to set the Id of new collection items to 0 and in the server use that to decide whether it's new or existing. Northwind however uses a composite key between OrderID and ProductID for OrderDetails. These will both have to be set by the client, so I can't find a way to detect whats new. Furthermore, a deleted OrderDetail won't exist in the detached graph and I will need to figure out what has been deleted and explicitly remove it.

Any advice would be much appreciated.

public override Order Update(Order entity)
{
    dbset.Attach(entity);
    DataContext.Entry(entity).State = EntityState.Modified;

    foreach (var orderDetail in entity.OrderDetails)
    {
        DataContext.Entry(orderDetail).State = EntityState.Modified;
    }

    return entity;
}
Daniel Revell
  • 8,338
  • 14
  • 57
  • 95

2 Answers2

6

I've recently been allowed to open source some work I did for my employer a while ago (with some changes of course). I actually wrote an extension method to solve this problem, you can get it at http://refactorthis.wordpress.com/2012/12/11/introducing-graphdiff-for-entity-framework-code-first-allowing-automated-updates-of-a-graph-of-detached-entities/

Hope it helps!

refactorthis
  • 1,893
  • 15
  • 9
4

This is common and complex issue and there is no magic which will do it for you. My solution (and the only one which works in all scenarios) was to load the Order again in your update method and manually merge changes:

public override Order Update(Order entity)
{
    // No attach of entity

    var attached = DataContext.Orders.Include(o => o.OrderDetails).SingleOrDefault(...);
    if (attached == null) ...

    // Merge changes from entity to attached - if you change any property
    // it will be marked as modified automatically

    foreach (var detail in attached.OrderDetails.ToList())
    {
        // ToList is necessary because you will remove details from the collection

        // if detail exists in entity check if it must be updated and set its state

        // if detail doesn't exists in entity remove if from collection - if it is \
        // aggregation (detail cannot exists without Order) you must also delete it 
        // from context to ensure it will be deleted from the database
    }

    foreach (var detail in entity.OrderDetails)
    {
        // if it doesn't exists in attached create new detail instance,
        // fill it from detail in entity and add it to attached entity - 
        //you must not use the same instance you got from the entity
    }

    DataContext.SaveChanges();

    return entity;
}

There can be also need to manually check timestamps if you use them.

Alternative scenario is what you have described with 0 used for new details and negative ID for deleted details but that is logic which must be done on client. It also works only in some cases.

Community
  • 1
  • 1
Ladislav Mrnka
  • 360,892
  • 59
  • 660
  • 670
  • Great answer. Thanks for taking the time! – Daniel Revell Oct 19 '11 at 08:42
  • Don't suppose you have any idea why the EF team decided to not to pursue the scenario of attaching entity graphs and make it a little more automatic? I'm thinking it might be due to not wanting to allow the client to update, say Product in a Order - OrderItem - Product scenario. – Daniel Revell Oct 19 '11 at 08:45
  • The more I think about this issue the more I believe that full automation is not possible or reliable (the reliability is exactly what you describe - automatic update could modify entities which you don't want to modify). – Ladislav Mrnka Oct 19 '11 at 08:59
  • I suppose it is more flexible letting you do it yourself but I can also see lots of benefits in doing it automatically. Perhaps the extension methods for .Includes could be extended further for reattaching where you can specify which relations need to be parsed. – Daniel Revell Oct 24 '11 at 08:08
  • See my answer below. I use a similar technique as you describe. – refactorthis Dec 14 '12 at 11:46