Search code examples
c#entity-framework-4navigation-properties

Generic way to retrieve Primary Keys from Navigation Properties


Given an entity, I would like to retrieve the primary keys of the related entities in a generic way. For instance: for a Customer, I would like to have a list of ID's of its Orders.

Current signature of my attempt:

GetPrimaryKeysOfRelatedEntities(DbContext db, object entity)

I'm able to retrieve the PK of an entity, and I'm able to retrieve navigation properties of an entity... but I can't get the PK of the navigation properties of an entity.

I'm missing a small link here!

Here's my code to get the Keys of entities, which doesn't work for navigation properties

private static IEnumerable<string> GetEntityType(DbContext db, Type entityType)
{
    entityType = ObjectContext.GetObjectType(entityType);

    var metadataWorkspace = ((IObjectContextAdapter)db).ObjectContext.MetadataWorkspace;
    var objectItemCollection = (ObjectItemCollection)metadataWorkspace.GetItemCollection(DataSpace.OSpace);

    ReadOnlyCollection<EntityType> entityTypes = metadataWorkspace.GetItems<EntityType>(DataSpace.OSpace);

    if (entityTypes == null)
    {
        throw new InvalidOperationException();
    }

    var ospaceType = entityTypes.SingleOrDefault(t => objectItemCollection.GetClrType(t) == entityType);

    if (ospaceType == null)
    {
        throw new ArgumentException(
            string.Format("The type '{0}' is not mapped as an entity type.", entityType.Name), "entityType");
    }

    return ospaceType.KeyMembers.Select(k => k.Name);
}

This code uses EntityType, and I propably should use something else, but I'm not sure what.


Solution

  • I can't see the problem, you have a great piece of code which can get primary keys of an entity by its type.

    You can use a reflection to enumerate properties of your entity type and call your code for type of these properties. For ex like this:

    var entityType = entity.GetType(); // or another type source
    foreach (var prop in type.GetProperties())
    {
         if (prop.PropertyType.IsGenericType && prop.PropertyType.GetGenericArguments().Any(x => x.Assembly == type.Assembly))
         {
             var navPropType = prop.PropertyType.GetGenericArguments().First(x => x.Assembly == type.Assembly);
             var keysForThisNavPropType = GetEntityType(db, navPropType);
         }
         else if (prop.PropertyType.Assembly == type.Assembly)
         {
             var keysForThisNavPropType = GetEntityType(db, prop.PropertyType);
         }
    }
    

    As you can see the criteria for finding nav properties is that its containing assambly is the same as your primary type.

    Edit

    Ok, try this:

            // we need DbContext vaule
            var db = YOUR_DB_CONTEXT; // assing db context here
            // So we can get ObjectContext instance
            var ctx = ((IObjectContextAdapter) db).ObjectContext;
            // we need some entity to check
            object entity = someYourEntity; // assign your entity here
            // let's get its type
            var type = entity.GetType();
    
            // helper function to get set name
            Func<Type, ObjectContext, string> getEntitySetByObjectType = (t, context) =>
                {
                    var container =
                        context.MetadataWorkspace.GetEntityContainer(context.DefaultContainerName, DataSpace.CSpace);
    
                    var entitySet =
                        container.BaseEntitySets.First(item => item.ElementType.Name.Equals(t.Name));
    
                    return container.Name + "." + entitySet.Name;
                };
    
            // go through the entity's properties
            foreach (var prop in type.GetProperties())
            {
                // nav properties which are collections
                if (prop.PropertyType.IsGenericType && prop.PropertyType.GetGenericArguments().Any(x => x.Assembly == type.Assembly))
                {
                    var val = (IEnumerable)prop.GetValue(entity);
                    if (val != null)
                    {
                        // get value and check if it is not null
                        string setName = null;
                        // go through collection values
                        foreach (var obj in val)
                        {
                            if (setName == null)
                                setName = getEntitySetByObjectType(obj.GetType(), ctx);
    
                            // get primary key values
                            var entityKey = ctx.CreateEntityKey(setName, obj);
                            Console.WriteLine(entityKey);
                        }
                    }
                }
                // nav props which are single objects
                else if (prop.PropertyType.Assembly == type.Assembly)
                {
                    // get value and check if it is not null
                    var val = prop.GetValue(entity);
                    if (val != null)
                    {
                        // get primary key values
                        var entityKey = ctx.CreateEntityKey(getEntitySetByObjectType(prop.PropertyType, ctx), val);
                        Console.WriteLine(entityKey);
                    }
                }
            }
    

    CreateEntityKey method returns object of EntityKey class (http://msdn.microsoft.com/ru-ru/library/system.data.entitykey(v=vs.110).aspx) which has EntityKeyValues property which is an array of EntityKeyMember. EntityKeyMember has properties Key and Value which are exactly primary key name and value.