实体框架代码优先。查找主键

37

如何找到Entity Framework Code First实体POCO的主键属性?

请注意,对于Id /类名+“Id”的字符串匹配是不好的选择。必须有某种方法来挖掘Entity Framework使用的约定并可靠地获取关键属性。

先行致谢。

5个回答

62

您可以询问映射元数据以获取关键属性的名称(可能有多个):

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

一旦你有键名,你就可以使用反射来访问它们的值。

正如你所看到的,这种方法会回归到ObjectContext API,因为DbContext API仅适用于简单情况,您不需要担心像映射元数据这样的细节。


2
谢谢。我想评论一下,将接口添加到上下文类(例如,IYourDataContext),并继承IObjectContextAdapter到IYourDataContext是一个更好的主意。这样你就不必强制转换它了。 - Sleeper Smith
这已经有5年历史了,有没有一种不需要使用重型反射的方法来实现呢?我的问题背景是尝试在一个基类中实现Find方法,但不能假定该实体的键名。谢谢大家。 - one.beat.consumer
有没有一种方法可以找出哪些属性上有(唯一)索引,而不是找到主键? - Rudey

28

如果有人需要帮助,我需要在不事先知道类型的情况下执行此操作(所以我不能轻松地执行CreateObjectSet<YourEntity>(),因为我不知道YourEntity),所以我能够将@Ladislav的解决方案调整为以下内容:

// 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;

看起来有些奇怪,MetadataWorkspace.GetType需要类型名称和命名空间的字符串,而不是System.Type,但这是我能找到的最好的方法。


谢谢,你救了我的命! - Hossein Shahdoost
有没有一种方法可以找出哪些属性具有(唯一)索引,而不是找到主键? - Rudey

14

在 EF 6.1 中有一个 Db() 扩展方法,使这个过程变得更容易。

示例:

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

它的性能如何?底层反射很重吗? - one.beat.consumer
1
我不知道,据我所知,EntityFramework 首先基于大量的反射。 - angularsen
3
此链接的软件包已经过时,不再列在NuGet上。 - Aaroninus
谢谢您指出这一点,很可能是由于EF的新版本而过时了。 - angularsen
5
或者是因为现在它的成本高达700美元。 - John
哇,那比我愿意为这个用例单独支付的费用要高得多 :-) - angularsen

9
由于表格的类型继承,以上两种方法都存在问题。我的实现版本(基于@S'pht'Kr的解决方案,但由于此原因使用DataSpace.OSpace而不是DataSpace.CSpace)如下:
        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;
        }

太棒了。那帮了我大忙。 - Bilal Fazlani

4

像用户 rashleighp 一样,我也想要一个用户 Ladislav Mrnka 答案的变体,它只需要在运行时知道类型,而不需要在编译时知道类型。与用户 rashleighp 一样,用户 S'pht'Kr 的解决方案对我无效,但他的解决方案有效。下面,我通过提供他的答案的一个更简单的版本来为这个对话做出贡献。但是,我刚刚了解到用户 anjdreas 的解决方案,我将使用那个。

// 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);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接