EntityBag Part VI – RelationshipEntry

Here’s the last piece in the EntityBag saga. RelationshipEntry is a small, DataContract serializeable class which wraps an ObjectStateEntry that represents a pair of related entities. It contains the name of the relationship, the state of the entry, and the key or index of the entity for each end organized by the role of that entity in the relationship. In addition to acting as a serializable container, this class provides methods which simplify applying the relationship to a context.

Fields and Properties

[DataContract]

internal class RelationshipEntry

{

    //

    // serializable properties

    //

    [DataMember] string RelationshipName { get; set; }

    [DataMember] EntityState State { get; set; }

    [DataMember] string Role1 { get; set; }

    [DataMember] EntityKey Key1 { get; set; }

    [DataMember] int AddedEntityIndex1 { get; set; }

    [DataMember] string Role2 { get; set; }

    [DataMember] EntityKey Key2 { get; set; }

    [DataMember] int AddedEntityIndex2 { get; set; }

    //

    // non-serializable properties

    //

    IEntityWithRelationships Entity1 { get; set; }

    IEntityWithRelationships Entity2 { get; set; }

Constructor

The interesting part of this constructor is the fact that it requires a Dictionary mapping from EntityKey to an index be passed in containing entries for any entities which are in the added state. This dictionary is created during the construction of the ContextSnapshot and the index is the position with the snapshot’s list of added entities. As described in previous posts, temp keys are unique based on object identity rather than values in the key, so their identity is lost in serialization and we have to use this other mechanism to make sure the identity is preserved.

//

// constructor

//

internal RelationshipEntry(ObjectStateEntry stateEntry, ObjectContext context,

                           Dictionary<EntityKey,int> addedEntityKeyToIndex)

{

    Debug.Assert(stateEntry.IsRelationship);

    this.RelationshipName = stateEntry.EdmType().FullName;

    this.State = stateEntry.State;

    this.Role1 = stateEntry.UsableValues().GetName(0);

    this.Key1 = (EntityKey)stateEntry.UsableValues().GetValue(0);

    if (context.GetEntityState(this.Key1) == EntityState.Added)

    {

        this.AddedEntityIndex1 = addedEntityKeyToIndex[this.Key1];

        this.Key1 = null;

    }

    this.Role2 = stateEntry.UsableValues().GetName(1);

    this.Key2 = (EntityKey)stateEntry.UsableValues().GetValue(1);

    if (context.GetEntityState(this.Key2) == EntityState.Added)

    {

        this.AddedEntityIndex2 = addedEntityKeyToIndex[this.Key2];

        this.Key2 = null;

    }

}

ResolveEntitiesAndKeys

This is a private method used in each of the public methods after the relationship entry has deserialized to lookup the keys and entities for the relationship (takes care of the added entity indexes, etc.).

void ResolveEntitiesAndKeys(ObjectContext context, List<IEntityWithKey> addedEntities)

{

    if (this.Key1 == null)

    {

        this.Entity1 = (IEntityWithRelationships)addedEntities[this.AddedEntityIndex1];

        this.Key1 = ((IEntityWithKey)this.Entity1).EntityKey;

    }

    else

    {

        this.Entity1 = (IEntityWithRelationships)context.GetEntityByKey(this.Key1);

    }

    if (this.Key2 == null)

    {

        this.Entity2 = (IEntityWithRelationships)addedEntities[this.AddedEntityIndex2];

        this.Key2 = ((IEntityWithKey)this.Entity2).EntityKey;

    }

    else

    {

        this.Entity2 = (IEntityWithRelationships)context.GetEntityByKey(this.Key2);

    }

}

Methods

The three methods for use by calling classes will attach, add or delete a relationship using the relationship manager on one of the related entities. The framework will automatically handle fixup for the other end.

//

// methods

//

internal void AddRelationship(ObjectContext context, List<IEntityWithKey> addedEntities)

{

    Debug.Assert(this.State == EntityState.Added);

    ResolveEntitiesAndKeys(context, addedEntities);

    IRelatedEnd relatedEnd = this.Entity1.RelationshipManager.GetRelatedEnd(this.RelationshipName,

        this.Role2);

    if (!relatedEnd.Contains(this.Key2))

    {

        relatedEnd.Add(this.Entity2);

    }

}

internal void AttachRelationship(ObjectContext context)

{

    Debug.Assert((this.State == EntityState.Deleted) || (this.State == EntityState.Unchanged));

    // Unchanged and deleted relationships cannot involve added entities, so no need for

    // addedEntities list.

    ResolveEntitiesAndKeys(context, null);

    IRelatedEnd relatedEnd = this.Entity1.RelationshipManager.GetRelatedEnd(this.RelationshipName,

        this.Role2);

    if (!relatedEnd.Contains(this.Key2))

    {

        relatedEnd.Attach(this.Entity2);

    }

}

internal void DeleteRelationship(ObjectContext context)

{

    Debug.Assert(this.State == EntityState.Deleted);

    // DeletedRelationships cannot involve added entities, so no need for addedEntities list

    ResolveEntitiesAndKeys(context, null);

    IRelatedEnd relatedEnd = this.Entity1.RelationshipManager.GetRelatedEnd(this.RelationshipName,

        this.Role2);

    if (relatedEnd.Contains(this.Key2))

    {

        relatedEnd.Remove(this.Entity2);

    }

}

Not too bad is it? I guess this is a testament to the power of decomposing a problem. If each piece is small enough and cohesive enough, the overall story can become much less complicated.

- Danny