Skip to content
This repository has been archived by the owner on Nov 5, 2019. It is now read-only.

PartialSaveWithEntityGraph

Daniel Svensson edited this page Apr 17, 2019 · 1 revision

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:

{code:c#} var shape = new EntityGraphShape() .Edge<Car, Wheel>(x => Wheels) .Edge<Car, Engine>(x => Engine); {code:c#} 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:

{code:c#} var graph = new EntityGraph(car, shape); {code:c#} 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:

{code:c#} var clone = graph.Clone(); {code:c#} 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:

{code:c#} var partialSaveContext = new CarExampleContext(); var clone = graph.Clone(partialSaveContext); {code:c#} 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:

{code:C#} partialSaveContext.SubmitChanges(); {code:c#} 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:

{code:c#} graph.Synchronize(clone); {code:c#} 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. {anchor:Disclaimer}

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}}:

{code:c#} var wheel = car.Wheels.First(); car.Wheels.Remove(wheel) {code:c#} 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.