EF Extension Methods Extravaganza part II – Relationship Entry & IRelatedEnd

In my last post I shared a few extension methods that I’ve found useful when working with the EF. Here are several more. First there is a set for working with ObjectStateEntry instances that track relationships. Before showing the code, though, let’s talk about the characteristics of relationship entries.

· Relationship entries can be in the added, deleted or unchanged states—but not in modified state.

· They have two properties which are the keys of the entities which participate in the relationship.

· The IExtendedDataRecord for either the current values record (if it’s added or unchanged) or the original values record (if it’s deleted) contains metadata describing the type of the relationship.

· For each key, the name of the column containing the key indicates the role that key plays in the relationship.

ObjectStateEntry.IsRelationshipForKey(EntityKey key)

One of the unfortunate things about relationship entries is that in the general case, a particular EntityKey could appear in either column of the IExtendedDataRecord. So, here’s a method which given an ObjectStateEntry, will determine if it is a relationship entry which involves a particular EntityKey. This is particularly useful as part of a predicate in a LINQ to Objects query over the ObjectStateManager.

public static bool IsRelationshipForKey(this ObjectStateEntry entry, EntityKey key)

{

    if (entry.IsRelationship == false)

    {

        return false;

    }

    return ((EntityKey)entry.UsableValues()[0] == key) || ((EntityKey)entry.UsableValues()[1] == key);

}

ObjectStateEntry.OtherEndKey(EntityKey thisEndKey)

Now that we have a relationship entry which involves a particular key, we may well want to know the key of the other end…

public static EntityKey OtherEndKey(this ObjectStateEntry relationshipEntry, EntityKey thisEndKey)

{

    Debug.Assert(relationshipEntry.IsRelationship);

    Debug.Assert(thisEndKey != null);

    if ((EntityKey)relationshipEntry.UsableValues()[0] == thisEndKey)

    {

        return (EntityKey)relationshipEntry.UsableValues()[1];

  }

    else if ((EntityKey)relationshipEntry.UsableValues()[1] == thisEndKey)

    {

        return (EntityKey)relationshipEntry.UsableValues()[0];

    }

    else

    {

        throw new InvalidOperationException("Neither end of the relationship contains the passed in key.");

    }

}

 

ObjectStateEntry.OtherEndRole(EntityKey thisEndKey)

…or the role of the other end…

public static string OtherEndRole(this ObjectStateEntry relationshipEntry, EntityKey thisEndKey)

{

    Debug.Assert(relationshipEntry != null);

    Debug.Assert(relationshipEntry.IsRelationship);

    Debug.Assert(thisEndKey != null);

    if ((EntityKey)relationshipEntry.UsableValues()[0] == thisEndKey)

    {

        return relationshipEntry.UsableValues().DataRecordInfo.FieldMetadata[1].FieldType.Name;

    }

    else if ((EntityKey)relationshipEntry.UsableValues()[1] == thisEndKey)

    {

        return relationshipEntry.UsableValues().DataRecordInfo.FieldMetadata[0].FieldType.Name;

    }

    else

    {

        throw new InvalidOperationException("Neither end of the relationship contains the passed in key.");

    }

}

When working with entity graphs, not only is it important to manipulate relationship entries, but you also need to handle instances of IRelatedEnd which is the interface implemented by both EntityReference<T> and EntityCollection<T>. RelationshipManager, the class on an entity which handles the relationships that the entity participates in, will allow you to retrieve an IRelatedEnd for a relationship. Unfortunately, though, sometimes you need to know what kind of relationship it is (collection or ref), and if it’s a ref you might want to retrieve or set the EntityKey so the following methods may come in handy:

IRelatedEnd.IsEntityReference()

public static bool IsEntityReference(this IRelatedEnd relatedEnd)

{

    Type relationshipType = relatedEnd.GetType();

    return (relationshipType.GetGenericTypeDefinition() == typeof(EntityReference<>));

   

}

IRelatedEnd.GetEntityKey()

public static EntityKey GetEntityKey(this IRelatedEnd relatedEnd)

{

    Debug.Assert(relatedEnd.IsEntityReference());

    Type relationshipType = relatedEnd.GetType();

    PropertyInfo pi = relationshipType.GetProperty("EntityKey");

    return (EntityKey)pi.GetValue(relatedEnd, null);

}

IRelatedEnd.SetEntityKey(EntityKey key)

public static void SetEntityKey(this IRelatedEnd relatedEnd, EntityKey key)

{

    Debug.Assert(relatedEnd.IsEntityReference());

    Type relationshipType = relatedEnd.GetType();

    PropertyInfo pi = relationshipType.GetProperty("EntityKey");

    pi.SetValue(relatedEnd, key, null);

}

Finally, a common task when you start generically handling related ends is to check to see if a particular related entity is already part of the relationship or not. So:

IRelatedEnd.Contains(EntityKey key)

public static bool Contains(this IRelatedEnd relatedEnd, EntityKey key)

{

    foreach (object relatedObject in relatedEnd)

    {

        Debug.Assert(relatedObject is IEntityWithKey);

        if (((IEntityWithKey)relatedObject).EntityKey == key)

        {

            return true;

        }

    }

    return false;

}

As you can see, generally these methods are pretty short and simple. There’s nothing really earth shattering, but as the library grows, many scenarios become a lot easier.

- Danny