EntityBag Part III – Public Surface and Serialization

Now that we can construct an EntityBag, we’ll continue pulling apart the top level class.

Public Surface

First, there are the public properties. The Mode is trivial, but in the case of the Root property, there are a couple of interesting parts: One is that retrieving the Root will automatically move the mode from Deserialized to LocalContext, and the other is that while EntityBag exposes a root entity, the backing field for the property is just the key not the whole entity—that way we don’t inadvertently serialize the root entity twice.

public Mode Mode { get; private set; }

       

public T Root

{

    get

    {

        if (this.Mode == Mode.Deserialized)

        {

            SwitchToLocalContext();

        }

        return (T) context.GetEntityByKey(this.rootKey);

    }

    internal set

    {

        this.rootKey = value.EntityKey;

    }

}

The SwitchToLocalContext method creates a private ObjectContext instance and plays changes from the ContextSnapshot into it. The other interesting bit is how keys for entities in the added state are serialized—because added entities always have temp keys, those keys don’t serialize well—all that comes across is the entity set name. So, for EntityBag we serialize added entities in an ordered list, and when we need to serialize a reference to a particular added entity we just store an index into that list.

void SwitchToLocalContext()

{

    Debug.Assert(this.Mode == Mode.Deserialized);

    this.context = new ObjectContext(this.snapshot.ConnectionString);

    this.snapshot.ApplyToContext(this.context);

    // Set the root key if root entity was added (in which case the root key is null)

    if (this.rootKey == null)

    {

        this.Root = (T)this.snapshot.GetAddedEntity(this.rootAddedEntityIndex);

    }

    this.Mode = Mode.LocalContext;

}

The Delete method is fairly straightforward—the only trick being to account for the mode:

public void Delete(object entity)

{

    switch (this.Mode)

    {

        case Mode.Deserialized:

            SwitchToLocalContext();

            break;

        case Mode.LocalContext:

            break;

        default:

            throw new InvalidOperationException("Can't delete when Mode == Constructed || Unwrapped.");

    }

    this.context.DeleteObject(entity);

}

UnwrapInto is a little trickier but the comments explain things fairly well.

public void UnwrapInto(ObjectContext context)

{

    switch(this.Mode)

    {

        case Mode.Deserialized:

            // This is the normal case for unwrap into -- just apply the snapshot

            this.snapshot.ApplyToContext(context);

            break;

        case Mode.LocalContext:

            // If we are operating on the local context, then we need to take a new snapshot,

            // detach things that will conflict and then apply.

            this.snapshot = new ContextSnapshot(this.context);

            // Detach unchanged and added entities from the internal context so the snapshot

            // can be applied elsewhere. Don't have to worry about modified entities because

            // only the modifiedOriginal versions are attached during the apply, and those

            // have been synthesized from the original values--they aren't attached.

            foreach (var entity in this.context.GetEntities(EntityState.Unchanged))

            {

                this.context.Detach(entity);

            }

            foreach (var entity in this.context.GetEntities(EntityState.Added))

            {

                // Detaching an added entity will clear its key -- so save before detaching

                // and then restore. The key is needed since it is what stores the entityset.

                EntityKey key = entity.EntityKey;

                this.context.Detach(entity);

                entity.EntityKey = key;

            }

            this.snapshot.ApplyToContext(context);

            this.snapshot = null;

            break;

        default:

            // mode == constructed || unwrapped

            throw new InvalidOperationException("Can only unwrap if Mode == Deserialized || LocalContext.");

    }

    // If we make it this far, then we have successfully unwrapped.

    this.snapshot = null;

    this.Mode = Mode.Unwrapped;

}

Serialization

When it comes to serialization, the EntityBag class is pretty straightforward because we delegate the hard parts to the ContextSnapshot class which we will examine in a future post. The first things to look at are the fields in the class... Given the previous method explanations, these should all be fairly obvious.

[DataMember] ContextSnapshot snapshot;

[DataMember] EntityKey rootKey;

[DataMember] int rootAddedEntityIndex;

ObjectContext context;

string connectionString;

The more interesting part is the custom code we add which gets run immediately before serialization and immediately after de-serialization. This code basically has two purposes: One is to deal with the multi-mode nature of the class by preparing the snapshot if it hasn’t already been taken, setting the mode to Deserialized after de-serializaiton, etc. The other is to handle serializing the added entity index for the root instead of just the EntityKey if the root entity is in the added state.

[OnSerializing]

void OnSerializing(StreamingContext streamingContext)

{

    switch(this.Mode)

    {

        case Mode.Unwrapped:

            throw new InvalidOperationException("Cannot serialize when Mode == Unwrapped.");

        case Mode.Constructed:

            this.snapshot = new ContextSnapshot(this.context, this.connectionString);

            this.PrepareRootEntityForSerialization();

            break;

        case Mode.LocalContext:

            this.snapshot = new ContextSnapshot(this.context);

            this.PrepareRootEntityForSerialization();

            break;

        default:

            // Mode.Deserialized -- nothing to do

            break;

    }

}

void PrepareRootEntityForSerialization()

{

    if (this.context.ObjectStateManager.GetObjectStateEntry(this.rootKey).State == EntityState.Added)

    {

        this.rootAddedEntityIndex = this.snapshot.AddedEntityKeyToIndex[this.rootKey];

        this.rootKey = null;

    }

}

 

[OnDeserialized]

void OnDeserialized(StreamingContext streamingContext)

{

    this.Mode = Mode.Deserialized;

}

That’s basically it for the EntityBag class itself. I know it’s a little hard to see the class as a whole when we’re looking at all the separate pieces, so once we’ve completed our survey of the whole system, I’ll see what I can do to make a zip file or something available with the source all in one place for your perusal. In the mean time, next post we’ll move on to looking at ContextSnapshot.

- Danny