【问题标题】:Entity framework 6 custom keys for table-per-type inheritance实体框架 6 个自定义键,用于按类型继承的表
【发布时间】:2016-11-14 15:45:23
【问题描述】:

我需要使用 EF6 和 CodeFirst 方法在新应用程序下转换一些现有数据库。我正在与继承中的映射约定作斗争。有一个最小(不)工作的例子:

假设我有两个表:一个 Parent 和一个 Child。

CREATE TABLE A_PARENT (
    A_PAR_ParentId UNIQUEIDENTIFIER PRIMARY KEY,
    A_PAR_data VARCHAR(255)
)

CREATE TABLE B_CHILD (
   B_CHL_ChildId UNIQUEIDENTIFIER PRIMARY KEY FOREIGN KEY REFERENCES A_PARENT(A_PAR_ParentId),
   B_CHL_childData VARCHAR(255)
)

我成功地解决了您可以看到的表格前缀,例如使用自定义属性的“A_PAR”。我相信 EF 完全知道哪个属性属于哪个列,哪个是主键。除了继承,一切都很好。因为当我尝试获取所有 Children 时,最终会出现 SQL 错误,因为 EF 会生成如下查询:

SELECT 
    '0X0X' AS [C1], 
    [Extent1].[A_PAR_ParentId] AS [A_PAR_ParentId], 
    [Extent1].[A_PAR_Data] AS [A_PAR_Data], 
    [Extent2].[B_CHL_ChildId] AS [B_CHL_ChildId], 
    [Extent2].[B_CHL_ChildData] AS [B_CHL_ChildData]
    FROM  [dbo].[A_PARENT] AS [Extent1]
    INNER JOIN [dbo].[B_CHILD] AS [Extent2] ON [Extent1].[A_PAR_ParentId] = [Extent2].[A_PAR_ParentId]

查询中唯一不正确的是连接谓词 - 表 B_CHILD 中没有 A_PAR_ParentId 这样的列。

在构建继承链时,如何强制EntityFramework使用实体的主键作为外键?我正在寻找一些基于约定的通用解决方案,因为数据库中的所有表都使用这种模式(如果类型/表是继承的 => 主键是父主键的外键并且没有复合键全部)。也许我正在寻找某种方式来告诉 EF PK 也是一个 FK 但没有导航属性。

--编辑:更多代码

模型非常简单:

    [ModulePrefix("A"), TablePrefix("PAR")]
    public class Parent
    {
        public Guid ParentId { get; set; }
        public string Data { get; set; }
    }

    [ModulePrefix("B"), TablePrefix("CHL")]
    public class Child : Parent
    {
        public Guid ChildId { get; set; }
        public string ChildData { get; set; }
    }

以及配置:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
            modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();

            modelBuilder.Types()
                .Where(type => !type.GetCustomAttributes(false).OfType<TableAttribute>().Any() && type.GetCustomAttributes(false).OfType<ModulePrefixAttribute>().Any())
                .Configure(config => config.ToTable(
                    ComposeDbName(GetModulePrefix(config.ClrType),
                    CamelCaseToUnderscore(GetClassName(config.ClrType)).ToUpper())
                    ));

            modelBuilder.Properties()
                .Where(property => property.DeclaringType.GetCustomAttributes(false).OfType<TablePrefixAttribute>().Any() && property.DeclaringType == property.ReflectedType)
                .Configure(config => config.HasColumnName(ComposeDbName(
                    GetModulePrefix(config.ClrPropertyInfo.DeclaringType),
                    GetTablePrefix(config.ClrPropertyInfo.DeclaringType),
                    config.ClrPropertyInfo.Name
                    )));
            modelBuilder.Properties()
                .Where(property => property.Name == property.DeclaringType.Name + "Id" && property.ReflectedType == property.DeclaringType)
                .Configure(config => config.IsKey());

            modelBuilder.Properties()
                .Where(property => property.PropertyType.IsClass || Nullable.GetUnderlyingType(property.PropertyType) != null)
                .Configure(config => config.IsOptional());
        }

还有一些更私有的方法使用列/表名称。我认为此时这并不重要,因为列/表名称似乎已得到很好的解析。

【问题讨论】:

  • 让我们看看与上述数据库表相关的 Code First 模型/配置。
  • 我刚刚在问题中添加了一些代码。
  • Child.ChildId 字段导致问题。
  • Ivan:看来你是对的。我刚刚看到一些例子说像你这样的话。所以我必须只将标识符属性放置到基类中吗?后代表的主键和它的名字呢?我不想把它命名为B_CHILD.B_CHL_ParentId,而是B_CHILD.B_CHL_ChildId

标签: c# entity-framework inheritance ef-code-first code-first


【解决方案1】:

如果类型/表是继承的 => 主键是父主键的外键

这正是 EF TPT 继承映射的工作原理,所以这里没有冲突。

问题在于Child 类中的ChildId 属性。由于Child 继承了Parent,它也继承了ParentId 属性,最终导致错误的TPT 映射。您必须删除它并将基类定义的属性用作 PK(和 FK),只需在派生表中为其指定不同的名称。

我建议您使用 Id 作为 PK 属性的名称,以便按照约定轻松关联不同的名称。

下面是修正后的模型的样子:

[ModulePrefix("A"), TablePrefix("PAR")]
public class Parent
{
    public Guid Id { get; set; }
    public string Data { get; set; }
}

[ModulePrefix("B"), TablePrefix("CHL")]
public class Child : Parent
{
    public string ChildData { get; set; }
}

以及基于该约定的配置:

modelBuilder.Types()
    .Where(type => !type.GetCustomAttributes(false).OfType<TableAttribute>().Any() && type.GetCustomAttributes(false).OfType<ModulePrefixAttribute>().Any())
    .Configure(config => config.ToTable(GetTableName(config.ClrType)));

modelBuilder.Properties()
    .Where(property => property.Name != "Id" && property.DeclaringType.GetCustomAttributes(false).OfType<TablePrefixAttribute>().Any() && property.DeclaringType == property.ReflectedType)
    .Configure(config => config.HasColumnName(GetColumnName(config.ClrPropertyInfo.DeclaringType, config.ClrPropertyInfo.Name)));

modelBuilder.Properties()
    .Where(property => property.Name == "Id" && property.DeclaringType.GetCustomAttributes(false).OfType<TablePrefixAttribute>().Any())
    .Configure(config => config.IsKey().HasColumnName(GetColumnName(config.ClrPropertyInfo.ReflectedType, "Id")));

除了你的之外,它还使用了两个新的私人助手:

static string GetTableName(Type entityType)
{
    return ComposeDbName(
        GetModulePrefix(entityType),
        CamelCaseToUnderscore(GetClassName(entityType)).ToUpper()
    );
}

static string GetColumnName(Type entityType, string propertyName)
{
    return ComposeDbName(
        GetModulePrefix(entityType),
        GetTablePrefix(entityType),
        propertyName == "Id" ? GetClassName(entityType) + "Id" : propertyName
    );
}

它的作用是按照惯例构建与此等效的内容:

modelBuilder.Entity<Parent>().ToTable("A_PARENT");
modelBuilder.Entity<Parent>().Property(e => e.Id).HasColumnName("A_PAR_ParentId");
modelBuilder.Entity<Parent>().HasKey(e => e.Id);
modelBuilder.Entity<Parent>().Property(e => e.Data).HasColumnName("A_PAR_Data");

modelBuilder.Entity<Child>().ToTable("B_CHILD");
modelBuilder.Entity<Child>().Property(e => e.Id).HasColumnName("B_CHL_ChildId");
modelBuilder.Entity<Child>().HasKey(e => e.Id);
modelBuilder.Entity<Child>().Property(e => e.ChildData).HasColumnName("B_CHL_ChildData");

【讨论】:

  • 难道不假定子表中的主/外键名称与父表的主键名称相同吗?
  • 建议的配置生成与显示的数据库表中完全相同的列名 - A_PAR_ParentIdB_CHL_ChildId
  • 建议使用统一的PK 属性名(在类中)并根据约定将其映射到每个表中的不同列名。
  • 感谢您的耐心等待,并对我的...感到抱歉。我尝试修复我的代码,然后复制粘贴您的代码,但结果相同(SQL 查询)像以前一样。我将在一个新的 VS 项目中从头开始再试一次。
  • 不客气。没问题,这正是我在发布之前所做的(在干净的环境中测试,最新的 EF6.1.3 如果重要的话)(只猜测自定义属性和私有方法)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-08
  • 1970-01-01
  • 2016-06-02
  • 2015-09-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多