EntityBag Part II – Modes and Constructor

In my last post I described the way the EntityBag class can be used. This time around we’ll take a look at the implementation of the top-level EntityBag class itself, and in subsequent posts we’ll dig into some supporting classes.

The first thing to understand about EntityBag is the fact that it has several different modes of operation. We need this one class as the bridge between mid-tier and client operations, and as such it has to operate different ways in different situations. Normally I try to avoid classes with modes since it’s easier to know what to expect of a class if it basically operates the same way regardless of its state. Further, if class does have modes I much prefer the transitions between modes to be very explicit. That said, after a fair amount of thought I came to the conclusion that the standard programming patterns for using EntityBag would just be a lot cleaner and easier if it had different modes of operation and switched between them automatically. Once that was decided, I figured the least that I could do to help would be to create an enum for the mode and use it both to add clarity to the implementation and to expose publicly what mode the class is in so that calling code can operate differently depending on the mode if necessary.

EntityBag has four modes:

public enum Mode

{

    Constructed,

    Deserialized,

    LocalContext,

    Unwrapped

}

When it is first constructed (typically on the mid-tier after retrieving some data to return to the client), it is in the “Constructed” mode. In this mode, the bag maintains a reference to an ObjectContext which is owned by the calling code, and as such the Delete and UnwrapInto methods are not allowed. In a typical scenario, the next step for the bag is that it is serialized, transported across a web service and then deserialized on the other side ending up in “Deserialized” mode. When in this mode, the bag has no ObjectContext—instead the entities and their state are stored in a DataContract compatible ContextSnapshot object. Also, when in this mode an attempt to access the Root entity or to call the Delete method will cause the bag to automatically switch to the next mode: LocalContext. The “LocalContext” mode is where the bag creates its own ObjectContext instance and replays the ContextSnapshot into it. The normal use in this mode is to make changes to the EntityGraph and have them automatically tracked by that local context, once these changes are complete, the bag will be serialized, transported back to the mid-tier and returned to Deserialized mode. Normally the next operation would be to UnwrapInto a context created in the mid-tier outside the bag—this operation pushes all of the changes into that context and in the process transfers ownership of the entities so that they may no longer be change tracked by the bag, etc. So the bag has a final “Unwrapped” mode which is something like being disposed. Once a bag is unwrapped it basically will no longer function at all.

The next interesting decision about EntityBag was its construction pattern. In order to create a new EntityBag, you need three things: an ObjectContext, the type of the Root object and the Root object instance. So I decided to make the class generic (to track the type) and to make the construction pattern a factory extension method to ObjectContext which takes the root object as a parameter. The result looks like this:

public static class EntityBagExtensionMethods

{

    // This public extension method is what you use to create an EntityBag:

    // var bag = ctx.CreateEntityBag<MyType>(myObject);

    public static EntityBag<T> CreateEntityBag<T>(this ObjectContext context, T root) where T : IEntityWithKey

    {

        return new EntityBag<T>(context, root);

    }

}

[DataContract]

public class EntityBag<T> where T : IEntityWithKey

{

    internal EntityBag(ObjectContext sourceContext, T root)

    {

        this.context = sourceContext;

        // NOTE: We need to capture the connection string now so we'll have it when serializing--in many

        // scenarios the connection will be closed and the connection string lost by the time we serialize.

        this.connectionString = sourceContext.Connection.ConnectionString;

        this.Root = root;

        this.Mode = Mode.Constructed;

    }

OK. There’s a start. In my next post we’ll look at the rest of the public surface and how serialization works.

- Danny