Share via


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

Comments

  • Anonymous
    January 27, 2008
    I tried again yestoday :), I created a new WCF project and Service method is okay,but wcf client looks not good after using 'Add service References...' in VS2008. The service side method:

   public EntityBag<Patient> GetPatientAndCharges(int PatientNum)        {            using (PatientEntities svc = new PatientEntities())            {                ObjectQuery<Patient> patients = svc.Patient;                var objPatient = (from patient in patients                                  where patient.PatientNumber == PatientNum                                  select patient).First();                objPatient.PatientCharges.Load();                return svc.CreateEntityBag<Patient>(objPatient);            }        }     public int UpdatePatientAndChanges(EntityBag<Patient> bagPatient)        {            using (PatientEntities svc = new PatientEntities())            {                int ret = -1;                bagPatient.UnwrapInto(svc);                ret = svc.SaveChanges();                return ret;            }

please find the detail error message as below:

Error 1 The type 'EntityBagTestClient.ClientSvc.EntityBagOfPatientqHGogXs4' cannot be used as type parameter 'T' in the generic type or method 'EntityBag.Lib.EntityBag<T>'. There is no implicit reference conversion from 'EntityBagTestClient.ClientSvc.EntityBagOfPatientqHGogXs4' to 'System.Data.Objects.DataClasses.IEntityWithKey'.

1.why does proxy generate a class as "EntityBagOfPatientqHGogXs4" ? 2. it seems EntityBag<T> can't be serialized ?  

  • Anonymous
    January 27, 2008
    I suspect the issue you are having may be either a result of not referencing all of the relevant types on the client so proxy classes are being used instead of the actual types or a matter of not making the serializer aware of the model's types.  You can take a look at the new version I put up on code gallery which has a full working service. If that doesn't help you debug your system, then let me know, and we'll keep investigating.
  • Danny
  • Anonymous
    January 28, 2008
    Danny Simmons wrote a class that persists the EntityState of objects in an ObjectContext so that they