EF代码优先配置中的多级继承

时间:2013-03-24 22:04:54

标签: entity-framework ef-code-first

我有一个我正在定义的几个实体的抽象基类。其中一个派生实体实际上是另一个实体的非抽象基类。

遵循以下代码:

public abstract class BaseReportEntry {
      public int ReportEntryId { get; set;}
      public int ReportBundleId { get; set; }  //FK
      public virtual ReportBundle ReportBunde { get; set; } 
}

//A few different simple pocos like this one
public PerformanceReportEntry : BaseReportEntry {
     public int PerformanceAbsolute { get; set; }
     public double PerformanceRelative { get; set; }

}

//And one with a second level of inheritance 
public ByPeriodPerformanceReportEntry : PerformanceReportEntry {
       public string Period { get; set; }
}

我正在使用基地EntityTypeConfiguration

public class BaseReportEntryMap<TReportEntry> : EntityTypeConfiguration<TReportEntry>
    where TReportEntry : BaseReportEntry
{
    public BaseReportEntryMap()
    {
        this.HasKey(e => e.ReportEntryId);

        this.HasRequired(e => e.ReportsBundle)
            .WithMany()
            .HasForeignKey(e => e.ReportsBundleId);
    }
}

据推测,这对于一级继承可以正常工作,但是对于那个具有第二级别的情况,会抛出以下错误:

The foreign key component 'ReportsBundleId' is not a declared property on type 'ByPeriodPerformanceReportEntry'


public class ByPeriodPerformanceReportEntryMap : BaseReportEntryMap<ByPeriodPerformanceReportEntry>
{
    public ByPeriodPerformanceReportEntryMap ()
        : base()
    {
        this.Property(e => e.Period).IsRequired();

        this.Map(m =>
        {
            m.MapInheritedProperties();
            m.ToTable("ByPeriodPerformanceReportEntries"); 
        });
    }
}

如果需要,这是ReportBundle类

 public class ReportsBundle 
{
    public int ReportsBundleId { get; set; }
    public virtual ICollection<PerformanceReportEntry> PerformanceReportEntries{ get; set; }
    public virtual ICollection<ByPeriodPerformanceReportEntry> ByPeriodPerformanceReportEntries{ get; set; }
}

1 个答案:

答案 0 :(得分:2)

问题不在于第二级继承,而是PerformanceReportEntryByPeriodPerformanceReportEntry的基础)是一个实体而BaseReportEntryPerformanceReportEntry的基础)不是。

如果PerformanceReportEntry不是实体,您的映射将起作用 - 即它的映射未添加到模型构建器配置中,并且此类型没有DbSet,并且它不会出现在导航中ReportsBundle中的集合。

在这种情况下无法从BaseReportEntryMap<ByPeriodPerformanceReportEntry>派生配置 - 并且没有必要,因为基本属性的映射已经由BaseReportEntryMap<PerformanceReportEntry>发生。因此,您可以使用

public class ByPeriodPerformanceReportEntryMap
    : EntityTypeConfiguration<ByPeriodPerformanceReportEntry>

但我怀疑所得到的模型是否符合您的预期。我不知道PerformanceReportEntries中的ByPeriodPerformanceReportEntriesReportsBundle个集合应该表达什么。您是否期望ByPeriodPerformanceReportEntries是由子类型过滤的集合?您是否希望PerformanceReportEntries仅包含PerformanceReportEntry但不包含ByPeriodPerformanceReportEntry s的ReportsEntries?您是否希望PerformanceReportEntries包含所有条目,包括ByPeriodPerformanceReportEntries

无论如何,BaseReportEntry.ReportBundle是映射在PerformanceReportEntry(不在ByPeriodPerformanceReportEntry中)的导航属性。这意味着类ReportsBundle中的反向导航属性必须引用PerformanceReportEntry这是PerformanceReportEntries导航集合。 ByPeriodPerformanceReportEntries将在ReportsBundleByPeriodPerformanceReportEntry之间引入第二个一对多关系(ByPeriodPerformanceReportEntry中没有导航属性)。 ByPeriodPerformanceReportEntries的反向导航属性不会是BaseReportEntry.ReportBundle

我的感觉是你不应该拥有ReportsBundle.ByPeriodPerformanceReportEntries集合,但我不确定你想要达到什么目的。

修改

根据您的评论,您只有这两种报告类型,我认为您的映射过于复杂。我会做以下事情:

  • 删除BaseReportEntry类并将其属性移至PerformanceReportEntry。拥有一个只有一个其他类派生自己的基类是没有意义的。

  • ByPeriodPerformanceReportEntries移除ReportsBundle,以便ReportsBundle为:

    public class ReportsBundle 
    {
        public int ReportsBundleId { get; set; }
        public virtual ICollection<PerformanceReportEntry>
            PerformanceReportEntries { get; set; }
    }
    
  • 删除BaseReportEntryMap并将映射移至PerformanceReportEntryMap。从EntityTypeConfiguration<PerformanceReportEntry>

  • 获取此地图
  • 更正映射。目前这是错误的,因为您没有在WithMany中指定反向导航属性。 PerformanceReportEntryMap应如下所示:

    public class PerformanceReportEntryMap
        : EntityTypeConfiguration<PerformanceReportEntry>
    {
        public PerformanceReportEntryMap()
        {
            this.HasKey(e => e.ReportEntryId);
    
            this.HasRequired(e => e.ReportsBundle)
                .WithMany(b => b.PerformanceReportEntries)
                .HasForeignKey(e => e.ReportsBundleId);
        }
    }
    
  • ByPeriodPerformanceReportEntryMap派生EntityTypeConfiguration<ByPeriodPerformanceReportEntry>并仅指定ByPeriodPerformanceReportEntry声明的属性的映射,而不是基本属性的映射。那已经发生在PerformanceReportEntryMap。您不需要也不能再次指定它,因为它会导致您的例外。

  • 使用Table-Per-Layer(TPH)继承而不是Table-Per-Concrete-Type(TPC),特别是如果您只在ByPeriodPerformanceReportEntry中声明了一些属性。 TPC更难以使用,因为它在数据库生成的身份和多态关联方面存在问题(您在PerformanceReportEntryReportsBundle之间的关系中存在这种关联)。 The problems are explained in more details here。相反,TPH提供最佳性能。 ByPeriodPerformanceReportEntryMap将如下所示:

    public class ByPeriodPerformanceReportEntryMap
        : EntityTypeConfiguration<ByPeriodPerformanceReportEntry>
    {
        public ByPeriodPerformanceReportEntryMap()
        {
            this.Property(e => e.Period).IsRequired();
        }
    }
    

    没有必要对TPH进行显式配置,因为它是默认的继承映射。