RIA Services Composition with Entity Framework

It’s recently come to our attention that the prescribed Update method pattern for RIA Services Composition* throws an Exception when multiple children are inserted. Specifically, you get the following error message when you submit your changes:

Submit operation failed. An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.

I’m not going to cover Composition as a whole in this post (for that, see the Compositional Hierarchies MSDN topic or Mathew’s introductory blog post). Instead, I want to explain the currently prescribed Update pattern and propose a new pattern that solves the multiple insert problem.

The Current (Slightly Broken) Compositional Update Method

In essence, our current documentation shows a parent Update method that does the following:

  1. Check if the parent has as original value in the ChangeSet.
    • If so, that means the parent was updated, so call AttachAsModified(), which attaches the entire object graph (parent and children).
      • The parent Entity’s EntityState is set to Modified.
      • Children with ChangeOperations of Insert, Update, and None are set to Unchanged.
      • Children with ChangeOperations of Delete remain Detached.
    • If not, call Attach(), which attaches the entire object graph (parent and all children).
      • The parent Entity’s EntityState is set to Unchanged.
      • Children with ChangeOperations of Insert, Update, and None are set to Unchanged.
      • Children with ChangeOperations of Delete remain Detached.
  2. Loop through all of the children and get them from their current Unchanged or Detached state into the correct state.

The Exception arises from the calls to Attach and AttachAsModified in step #1. With EF, when you attach the parent Entity, all children are attached as well. This is a problem, as all of your newly added children will have keys of zero by default, which isn’t allowed. This is where you get the Exception that you eventually see on the client.

I’ve seen some forum reports of people managing this by subtracting one from each newly created Entity on the client so that new Entities have negative keys, but you shouldn’t have to manage your new Entity keys like that.

The Revised Compositional Update Method

Entity Framework does allow multiple Entities to have the same key if they are in the process of being Added. Using this information, the revised Update method takes the following approach:

  1. Call AddObject on the parent, which attaches the entire object graph (parent and children).
    • The parent Entity’s EntityState is set to Added.
    • Children with ChangeOperations of Insert, Update, and None are set to Added.
    • Children with ChangeOperations of Delete remain Detached.
  2. Check if the parent has an original value in the ChangeSet.
    • If so, that means the parent was updated, so call AttachAsModified(), which changes the parent’s EntityState from Added to Modified.
    • If not, change the parent’s EntityState to Unchanged.
  3. Loop through all of the children and get them from their current Added or Detached state into the correct state.

You can find the code below. This method assumes that Parent contains an EntityCollection that has been marked with the Include and Composition attributes:

public void UpdateParent(Parent currentParent)  
{ 
    // Add the entire graph so duplicate keys on new Entities are allowed.
    this.ObjectContext.Parents.AddObject(currentParent);

    // Get the root Entity into the correct state.
    Parent original = this.ChangeSet.GetOriginal(currentParent);

    if (original == null) 
    {
        this.ObjectContext.ObjectStateManager.ChangeObjectState(currentParent,
            EntityState.Unchanged);
    } 
    else
    {
        this.ObjectContext.Parents.AttachAsModified(currentParent, original); 
    }

    // Loop through all children and get them into the correct state.
    foreach (Child c in
                this.ChangeSet.GetAssociatedChanges(currentParent, p => p.Children)) 
    {  
        ChangeOperation change = this.ChangeSet.GetChangeOperation(c);
        switch (change) 
        {  
            case ChangeOperation.Delete: 
                if (c.EntityState == EntityState.Detached) 
                { 
                    this.ObjectContext.Children.Attach(c); 
                } 
                this.ObjectContext.DeleteObject(c); 
                break; 
            case ChangeOperation.Insert: 
                // This is already done for us.
                break; 
            case ChangeOperation.None: 
                this.ObjectContext.ObjectStateManager.ChangeObjectState(c,
                    EntityState.Unchanged); 
                break; 
            case ChangeOperation.Update: 
                this.ObjectContext.Children.AttachAsModified(c,
                    this.ChangeSet.GetOriginal(c)); 
                break; 
            default: 
                break; 
        } 
    }
}

I’ve written a set of tests against this new pattern (for an upcoming post) and it appears to behave exactly as we want.

I should mention that this is simply a recommended pattern to get compositional updates working. If you have business logic specific to your application, you may need to modify this method to suit your needs.

*At the time of this writing, the UpdateSalesOrder() method in the Compositional Hierarchies MSDN topic exhibits the issue. We’re working to get the topic updated.