Compartir a través de


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

Comments

  • Anonymous
    January 23, 2008
    Can you be sure to include a look at the payload and it's size for a smallish entity as a point of reference? Thanks (I acutally created and account on the msdn blogs for the first time ever JUST to leave this comment!)

  • Anonymous
    January 23, 2008
    Will do.  In fact, once I finish the last post with code, I'll probably do a follow-on post thinking about ways to improve perf across various axes (reduce computation, reduce payload size, etc.) as well as other future directions.

  • Danny
  • Anonymous
    January 23, 2008
    System.Data.Objects.ObjectContext does not contain a extension method definition for "CreateOriginalValuesObjectWithReferences"

  • Anonymous
    January 24, 2008
    CreateOriginalValuesObjectWithReferences is another extension method I created to simplify some cases.  I guess it was left out of the extension methods series... In any case I've now published a .zip file with the whole project in it which is probably the easiest way to get this working.  You can find it at http://code.msdn.microsoft.com/entitybag

  • Anonymous
    January 24, 2008
    Hi Daniel,You are right, I created the project in VS 2008 yesterday, and I followed your footsteps of your blog step by step, but I can't find the "CreateOriginalValuesObjectWithReferences" as you said:) Anyway I have found it later on with your project. Thanks for your reply and the project url.

  • Anonymous
    January 28, 2008
    Danny Simmons wrote a class that persists the EntityState of objects in an ObjectContext so that they