Sleeper Smith
Sleeper Smith

Reputation: 3242

Entity Framework code first. Find primary key

How do I find which property of a class is the primary key of the Entity Framework Code First entity POCO?

Please note string matching for Id / class name + "Id" is a bad option. There must be some way to dig out the convention used by Entity Framework and reliably getting the key property.

Thanks in advance.

Upvotes: 37

Views: 26343

Answers (5)

Tyson Williams
Tyson Williams

Reputation: 1725

Like user rashleighp, I also wanted a variant of user Ladislav Mrnka's answer that only needs to know the type at runtime instead of needing to know the type at compile time. Also like user rashleighp, the solution of user S'pht'Kr didn't work for me, but his solution did work. Below, I contribute to this conversation by providing a simpler version of his answer that worked for me. However, I just learned about the solution by user anjdreas and that is the one that I will be using.

// variable "type" is a System.Type passed in as a method parameter
((IObjectContextAdapter)context)
    .ObjectContext
    .MetadataWorkspace
    .GetItem<EntityType>(type.FullName, DataSpace.OSpace)
    .KeyProperties
    .Select(p => p.Name);

Upvotes: 4

S&#39;pht&#39;Kr
S&#39;pht&#39;Kr

Reputation: 2839

In case it helps anyone, I needed to be able to do this without the knowing the type beforehand (so I couldn't easily do CreateObjectSet<YourEntity>() because I didn't know YourEntity), so I was able to adapt @Ladislav 's solution into the following:

// variable "type" is a System.Type passed in as a method parameter
ObjectContext objectContext = ((IObjectContextAdapter)this.context).ObjectContext;
IEnumerable<string> retval = (IEnumerable<string>)objectContext.MetadataWorkspace
    .GetType(type.Name, type.Namespace, System.Data.Entity.Core.Metadata.Edm.DataSpace.CSpace)
    .MetadataProperties
    .Where(mp => mp.Name == "KeyMembers")
    .First()
    .Value;

Seems kind of odd that MetadataWorkspace.GetType requires strings of the type name and namespace instead of a System.Type, but that's the best I could find.

Upvotes: 28

angularsen
angularsen

Reputation: 8668

In EF 6.1 there is a Db() extension method that makes this easier.

Example:

public static IEnumerable<string> GetPrimaryKeyPropertyNames(DbContext db, Type entityType)
{
    return db.Db(entityType).Pks.Select(x => x.PropertyName);
}

Upvotes: 14

rashleighp
rashleighp

Reputation: 1226

I had problem with both the above approaches because of Table Per Type inheritance. My working version (based on @S'pht'Kr's solution but using DataSpace.OSpace not DataSpace.CSpace for this reason) is below:

        protected IEnumerable<string> GetKeyPropertyNames()
        {
            var objectContext = ((System.Data.Entity.Infrastructure.IObjectContextAdapter) this.Context).ObjectContext;

            return GetKeyPropertyNames(typeof (TEntity), objectContext.MetadataWorkspace);
        }

        private static IEnumerable<string> GetKeyPropertyNames(Type type, MetadataWorkspace workspace)
        {
            EdmType edmType;

            if (workspace.TryGetType(type.Name, type.Namespace, DataSpace.OSpace, out edmType))
            {
                return edmType.MetadataProperties.Where(mp => mp.Name == "KeyMembers")
                    .SelectMany(mp => mp.Value as ReadOnlyMetadataCollection<EdmMember>)
                    .OfType<EdmProperty>().Select(edmProperty => edmProperty.Name);
            }

            return null;
        }

Upvotes: 9

Ladislav Mrnka
Ladislav Mrnka

Reputation: 364249

You can ask mapping metadata to get names of key properties (there can be more then one):

ObjectContext objectContext = ((IObjectContextAdapter)dbContext).ObjectContext;
ObjectSet<YourEntity> set = objectContext.CreateObjectSet<YourEntity>();
IEnumerable<string> keyNames = set.EntitySet.ElementType
                                            .KeyMembers
                                            .Select(k => k.Name);

Once you have key names you can use reflection to access their values.

As you can see the approach reverts back to ObjectContext API because DbContext API is only for simple scenarios where you don't bother with such details like mapping metadata.

Upvotes: 63

Related Questions