EntityBag Part V – ContextSnapshot Constructing and Applying

For this post, I’m going to set a new personal record for least prose/most code. We’re going to look at the core of ContextSnapshot, and the code includes extensive comments so we’ll mostly let it speak for itself.

Constructors

We have two different constructors because in some scenarios with EntityBag it’s most convenient to construct the snapshot by just passing an ObjectContext, while in others the connection string in the ObjectContext has been cleared, so it’s necessary to explicitly pass the connection string in addition to the context. Regardless of the passed in parameters, the core piece of the constructor creates the lists of entities and relationships by state. The code below makes extensive use of the methods I described previously in my Extension Methods Extravaganza posts and in my posts about creating original values objects.

//

// constructors

//

public ContextSnapshot(ObjectContext context) : this(context, context.Connection.ConnectionString)

{

}

public ContextSnapshot(ObjectContext context, string connectionString)

{

    this.ConnectionString = connectionString;

    // For unchanged entities we only need the current values.

    this.unchangedEntities = new List<IEntityWithKey>(context.GetEntities(EntityState.Unchanged));

    // For modified entities we need current plus the original values object along with EntityReferences

    // (the addition of references to the original values object here is critical since this is how

    // 1-1 and 1-many relationships are serialized for modified entities since it's the original values

    // object which is attached on modified entities. In the case of deleted entities, the

    // relationships don't exist, and in the cases of added or unchanged entities the current values

    // objects bring along the relationships and they are what are added or attached.

    this.modifiedEntities = new List<IEntityWithKey>();

    this.modifiedOriginalEntities = new List<IEntityWithKey>();

    foreach (var entity in context.GetEntities(EntityState.Modified))

    {

        this.modifiedOriginalEntities.Add((IEntityWithKey)context

            .CreateOriginalValuesObjectWithReferences(entity));

        this.modifiedEntities.Add(entity);

    }

    // For deleted, just the original values.

    this.deletedEntities = new List<IEntityWithKey>();

    foreach (var entity in context.GetEntities(EntityState.Deleted))

    {

        this.deletedEntities.Add((IEntityWithKey)context.CreateOriginalValuesObject(entity));

    }

    // For added entities we need the current values, plus we need to make an index mapping from

    // the key to where the entity lands in the added entities list so that we can serialize those

    // index values where we would have serialized the added entity key.

    this.addedEntities = new List<IEntityWithKey>();

    this.addedEntityKeyToIndex = new Dictionary<EntityKey, int>();

    int index = 0;

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

    {

        this.addedEntities.Add(entity);

        this.addedEntityKeyToIndex.Add(entity.EntityKey, index);

        index++;

    }

    // Also serialize lists of added, deleted relationships.

    this.addedRelationships = new List<RelationshipEntry>();

    foreach (var stateEntry in context.GetRelationships(EntityState.Added))

    {

        this.addedRelationships.Add(new RelationshipEntry(stateEntry, context,

            this.addedEntityKeyToIndex));

    }

    this.deletedRelationships = new List<RelationshipEntry>();

    foreach (var stateEntry in context.GetRelationships(EntityState.Deleted))

    {

        this.deletedRelationships.Add(new RelationshipEntry(stateEntry, context,

            this.addedEntityKeyToIndex));

    }

    // Serialize unchanged many-to-many relationships

    this.unchangedManyToManyRelationships = new List<RelationshipEntry>();

    foreach (var stateEntry in context.GetUnchangedManyToManyRelationships())

    {

        this.unchangedManyToManyRelationships.Add(new RelationshipEntry(stateEntry, context,

            this.AddedEntityKeyToIndex));

    }

}

ApplyToContext

The final piece of this class is the method which will take the lists of entities and relationships that the constructor created and play them back into a context. Original values are attached, added entities added, modified entities have their property changes “applied” and deleted entities are deleted. Similar operations are performed for explicitly added and deleted relationships.

public void ApplyToContext(ObjectContext context)

{

    // Attach all the unchanged entities.

    foreach (IEntityWithKey entity in this.unchangedEntities)

    {

        context.Attach(entity);

    }

    // Attach & apply changes to modified entities

    foreach (IEntityWithKey entity in this.modifiedOriginalEntities)

    {

        context.Attach(entity);

    }

    foreach (IEntityWithKey entity in this.modifiedEntities)

    {

        context.ApplyPropertyChanges(entity.EntityKey.EntityContainerName + "." +

                entity.EntityKey.EntitySetName, entity);

    }

    // Add entities & relationships

    foreach (IEntityWithKey entity in this.addedEntities)

    {

        // have to remove the entity key from the object so add will succeed

        string fullEntitySetName = entity.EntityKey.GetFullEntitySetName();

        entity.EntityKey = null;

        context.AddObject(fullEntitySetName, entity);

    }

    foreach (RelationshipEntry re in this.addedRelationships)

    {

        re.AddRelationship(context, this.addedEntities);

    }

    // to deal with deleting entities & relationships properly, we need a special dance:

    // - attach all the deleted entities & the deleted relationships

    // - delete the entities

    // - delete the relationships

    foreach (IEntityWithKey entity in this.deletedEntities)

    {

      context.Attach(entity);

    }

    foreach (RelationshipEntry re in this.deletedRelationships)

    {

        re.AttachRelationship(context);

    }

    foreach (IEntityWithKey entity in this.deletedEntities)

    {

        context.DeleteObject(entity);

    }

    foreach (RelationshipEntry re in this.deletedRelationships)

    {

        re.DeleteRelationship(context);

    }

    foreach (RelationshipEntry re in this.unchangedManyToManyRelationships)

    {

        re.AttachRelationship(context);

    }

    // Clear lists. Once a snapshot has been applied, you cannot re-apply it

    // since the entities have been attached.

    this.unchangedEntities = null;

    this.modifiedOriginalEntities = null;

    this.modifiedEntities = null;

    this.deletedEntities = null;

    this.addedEntities = null;

}

That’s it. Hopefully you’ll find this all clear—if not, don’t hesitate to ask some questions. Next post we’ll wrap up EntityBag with a look at the RelationshipEntry class.

- Danny