How to get primary key value with Entity Framework Core

37.7k views Asked by At

We are currently using the method below which depends upon IObjectContextAdapter in an abstract Repository. From what I'm reading, it appears that anything related to ObjectContext is being chopped out of Entity Framework Core. The method below is the only place we depend upon anything related to ObjectContext.

We'd like to upgrade to Entity Framework Core. This is our only road block. Is there a way to get the value of the primary key of an entity with the Entity Framework Core apis?

// Entity Framework
public virtual int GetKey(T entity)
{
    var oContext = ((IObjectContextAdapter)_DbContext).ObjectContext;
    var oSet = oContext.CreateObjectSet<T>();
    var keyName = oSet.EntitySet.ElementType
                                .KeyMembers
                                .Select(k => k.Name)
                                .Single();

    return (int)entity.GetType().GetProperty(keyName).GetValue(entity, null);
}
3

There are 3 answers

7
YuriyP On BEST ANSWER

I also faced with similar problem and found the following solution

// Entity Framework Core
public virtual int GetKey<T>(T entity)
{
    var keyName = Context.Model.FindEntityType(typeof (T)).FindPrimaryKey().Properties
        .Select(x => x.Name).Single();

    return (int)entity.GetType().GetProperty(keyName).GetValue(entity, null);
}
1
tb-mtg On

Based on YuriyP's answer you could also get composite key info:

 public static class Extensions {

    public static IEnumerable<string> FindPrimaryKeyNames<T>(this DbContext dbContext, T entity) {
      return from p in dbContext.FindPrimaryKeyProperties(entity) 
             select p.Name;
    }

    public static IEnumerable<object> FindPrimaryKeyValues<T>(this DbContext dbContext, T entity) {
      return from p in dbContext.FindPrimaryKeyProperties(entity) 
             select entity.GetPropertyValue(p.Name);
    }

    static IReadOnlyList<IProperty> FindPrimaryKeyProperties<T>(this DbContext dbContext, T entity) {
      return dbContext.Model.FindEntityType(typeof(T)).FindPrimaryKey().Properties;
    }

    static object GetPropertyValue<T>(this T entity, string name) {
      return entity.GetType().GetProperty(name).GetValue(entity, null);
    }

  }

Which could then be used in like so:

var keyValues = context.FindPrimaryKeyValues(entity);    
var existingEntity = context.Set<TEntity>().Find(keyValues);
0
Pavel Voronin On

At least in EF Core 2.1 you can get the key this way:

var entry = dbContext.Entry(entity);
object[] keyParts = entry.Metadata.FindPrimaryKey()
             .Properties
             .Select(p => entry.Property(p.Name).CurrentValue)
             .ToArray();

This allows you to get the key which is created as a shadow property.

I do not know how performant is the call Property(p.Name).CurrentValue, nevertheless it is still possible to cache the names of properties of the key:

private static readonly ConcurrentDictionary<Type,string[]> KeyPropertiesByEntityType = new ConcurrentDictionary<Type, string[]>();

public object[] KeyOf<TEntity>(TEntity entity) where TEntity : class
{
    Guard.ArgumentNotNull(entity, nameof(entity));

    var entry = _dbContext.Entry(entity);
    var keyProperties = KeyPropertiesByEntityType.GetOrAdd(
        entity.GetType(),
        t => entry.Metadata.FindPrimaryKey().Properties.Select(property => property.Name).ToArray());

    var keyParts = keyProperties
        .Select(propertyName => entry.Property(propertyName).CurrentValue)
        .ToArray();

    return keyParts;
}

public TKey KeyOf<TEntity, TKey>(TEntity entity) where TEntity : class
{
    var keyParts = KeyOf(entity);
    if (keyParts.Length > 1)
    {
        throw new InvalidOperationException($"Key is composite and has '{keyParts.Length}' parts.");
    }

    return (TKey) keyParts[0];
}