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:
- Create a new instance of CarExampleContext called partialSaveContext
- Duplicate the entity graph for car in this new context
- Call SubmitChanges() on partialSaveContext
- 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.