EntityBag Part IV – ContextSnapshot Fields and Properties

Well, we’re moving right along—at the fourth part in this series already. I imagine, though, that some of you may be chomping at the bit because you’ve waded through three posts worth of my ramblings without really getting to the hard part yet. At the end of the day we’re basically looking at how to serialize the ObjectContext, and in my last post I mentioned that EntityBag just delegates responsibility for that to another class. Well, in this post we’re going to start looking at ContextSnapshot which is DataContract serializable and can transport the entire contents of an ObjectContext over the wire. (On a side note, have any of you spent much time working in a Literate Programming system—something like Donald Knuth’s Web? If so, maybe this series of posts with code and prose intermingled will generate some of the same nostalgia for you that it does for me. <grin>)

The DataMembers and Other Fields

Essentially the way we manage serialization of the context is just by transporting a number of lists of entities and of relationships organized by states. For unchanged and added entities, only the current values are kept. With deleted entities, we keep the original values only, and with modified entities of course we need both an original and a current version. In each case we automatically serialize the EntityKey of related entities if the relationship is an EntityReference and this ends up bringing along enough information for all 1-1 and 1-many relationships. For many-to-many relationships, however, we have to explicitly serialize the relationship info as we need also to do for added and deleted relationships so that we can effectively recreate the changes. Beyond these lists, we also serialize the connection string which is required when it later comes time to construct a local ObjectContext for change tracking, and the class maintains a dictionary which maps from the EntityKey of added entities to their index in the added entity list. This dictionary is not serialized, but is used to help with constructing anything which must reference added entities in a way that is preserved across serialization (like relationship entries or the root entity for EntityBag).

[DataContract]

public class ContextSnapshot

{

    //

    // serialized members

    //

    [DataMember] string connectionString;

    [DataMember] List<IEntityWithKey> unchangedEntities;

    [DataMember] List<IEntityWithKey> modifiedEntities;

    [DataMember] List<IEntityWithKey> modifiedOriginalEntities;

    [DataMember] List<IEntityWithKey> deletedEntities;

    [DataMember] List<IEntityWithKey> addedEntities;

    [DataMember] List<RelationshipEntry> addedRelationships;

    [DataMember] List<RelationshipEntry> deletedRelationships;

    [DataMember] List<RelationshipEntry> unchangedManyToManyRelationships;

    //

    // non-serialized members

    //

    Dictionary<EntityKey, int> addedEntityKeyToIndex;

One interesting bit here is that when the ConnectionString property is set on the context snapshot we clear the provider connection string portion of it for a few reasons: First, it may contain sensitive information like a username and password which we don’t want travelling over the web service or available on the client. Secondly, this part of the overall connection string is not needed to successfully construct the context (what is needed is the location of the metadata and the name of the store provider). Finally, even if we were willing to send that information to the client, the expectation is that the client context won’t have line-of-sight to the database anyway, so it’s better to clear the connection string and get an immediate error if any operation is attempted on the client which would require a connection to the database.

//

// properties

//

public string ConnectionString

{

    get

    {

        return this.connectionString;

    }

    private set

    {

        // Clear the store provider connection string because it could contain a password

        // or something else we don't want to send to the client.

        var csBuilder = new EntityConnectionStringBuilder(value);

        csBuilder.ProviderConnectionString = null;

        this.connectionString = csBuilder.ConnectionString;

    }

}

public Dictionary<EntityKey, int> AddedEntityKeyToIndex

{

    get

    {

        return this.addedEntityKeyToIndex;

    }

}

public IEntityWithKey GetAddedEntity(int addedEntityIndex)

{

    return this.addedEntities[addedEntityIndex];

}

That sets the stage for the core logic of ContextSnapshot which is the subject of the next installment in this series.

- Danny