How to partially save entities in your domain context?

Introduction

A RIA services domain context is used to manage your entities and to submit changes to your domain service following the unit of work pattern. The domain context gives little or no control over what entities are going to be communicated to a domain service when the SubmitChanges() method is invoked. There are many situations where just submitting all pending changes is too coarse grained. In this post I will explain how EntityGraph can give control over which entities are submitted to your domain context (but see Disclaimer below).

EntityGraph in a Nutshell

An entity graph is defined as a collection of entities connected through their associations that forms a logical unit. An entity graph is defined by means of an entity graph shape. For example:

var shape = new EntityGraphShape()
   .Edge<Car, Wheel>(x => Wheels)
   .Edge<Car, Engine>(x => Engine);
This defines the shape of an entity graph spanning the associations between Car, its Wheels, and its Engine.

Given an Car instance called car, we can create a corresponding EntityGraph as follows:

var graph = new EntityGraph(car, shape);
There are many things you can now do with this entity graph. E.g., see http://riaservicescontrib.codeplex.com/wikipage?title=EntityGraphs.

For partial save I'm going to focus on the Clone() method. I.e., we can create a clone of car like this:

var clone = graph.Clone();
This example creates an identical copy of car and all entities that are reachable according to the edges in shape (i.e., it includes identical copies of the wheels and the engine of the car).

Implementing Partial Save

Lets assume that car is contained in the domain context CarExampleContext and that we want to submit the changes of this single Car instance together with its associated wheels and engine. This basically means that we want to submit only the pending changes for the entities contained in the entity graph we just created. Of course, we can't just call SubmitChanges() on the CarExampleContext because that may also submit pending changes of entities that are not part of the graph.

For a partial save of car, this is what we are going to do:
  1. Create a new instance of CarExampleContext called partialSaveContext
  2. Duplicate the entity graph for car in this new context
  3. Call SubmitChanges() on partialSaveContext
  4. On success, synchronize the entities in the original domain context with the entities in partialSaveContext.
A clone cannot be present in the same domain context as its source. The reason is that the cloned entities have the same primary keys. Adding them will lead to an exception indicating that an entity with the same key is already present in the context. The Clone() method that we used above therefore returns a cloned entity (and its associations) that is not attached to any domain context. A consequence is that the cloned entities no longer contain state information (since they are not managed by a context). They all have an EntityState of EntityState.Detached. Attaching them to partialSaveContext will give them an EntityState of EntityState.Unmodified. HasChanges will return false and calling SubmitChanges() will have no effect. This is clearly not what we have in mind for a partial save method.

In order to make a clone of car that includes proper state information, the cloned entities must be attached to a domain context. For this purpose, EntityGraph also provides a state-preserving Clone() method. It attaches the cloned entities to another context and restores the entity state. This is the method we're going to use:

var partialSaveContext = new CarExampleContext();
var clone = graph.Clone(partialSaveContext);
After calling Clone(), partialSaveContext will contain a clone of car and all entities in this domain context have the same EntityState as the entities in the entity graph graph. This means that partialSaveContext.HasChanges yields true and that we can now call SubmitChanges() on the context:

partialSaveContext.SubmitChanges();
This will submit the pending changes of the cloned entity graph rooted at car to the server. Nothing more, nothing less.

If SubmitChanges() completes without an error, you can verify that partialSaveContext.HasChanges yields false, as expected. However, the original context still has changes pending. This can be observed by inspecting context.HasChanges and graph.HasChanges. Both properties yield true. This is because the source domain context still contains the original entities with their changes pending. Moreover, since we've submitted the changes to the server, some of the properties of the entities in partialSaveContext may have been updated with server-provided values (this is for instance the case with generated keys). As a final step we therefore have to synchronize the entities in the original context with the entities in partialSaveContext. This can be done with the Synchronize method of EntityGraph:

graph.Synchronize(clone);
This updates the entities in the EntityGraph graph according to the entities in the EntityGraph clone. This includes updating the entity states. As a result, graph and clone are identical clones and both have no more pending changes (you can verify that all entities in graph have their EntityState set to EntityState.Unmodified, exactly what is needed. This completes the partial save mechanism. Observe that partialSaveContext is no longer needed.

Disclaimer

There is one situation where the partial save method will/can not work correctly. This happens when an entity falls out the entity graph because a foreign key of the entity is changed. For example, by removing a wheel from car:

var wheel = car.Wheels.First();
car.Wheels.Remove(wheel)
Changing the foreign key forms a change of the owning entity (wheel in the example). But since it is no longer part of the entity graph, its change will not be submitted to the server during a partial save. The result is that after a partial save, the original context still has pending changes. It is not obvious how to deal with this. Please let me know if you have a solution.

Last edited Dec 1, 2011 at 9:31 AM by MdeJ, version 9

Comments

No comments yet.