EF CodeFirst系列(3)---EF中的继承策略(暂存)

时间:2023-03-09 16:05:53
EF CodeFirst系列(3)---EF中的继承策略(暂存)

  我们初始化数据库一节已经知道:EF为每一个具体的类生成了数据库的表。现在有了一个问题:我们在设计领域类时经常用到继承,这能让我们的代码更简洁且容易管理,在面向对象中有“has  a”和“is a”关系(如student has a name,student is a person--继承),然而数据库中只有“has a”关系。数据库管理系统并不支持继承,所以我们怎么去映射具有继承关系的领域类呢?

EF CodeFirst中有三种方式表示继承体系:

1.TPH(table per hierarchy): 这是EF的默认方式,这种方式把整个继承体系都映射在一个表中,通过一个discriminator(鉴别器)列来判断继承关系。如Student继承于Person类,那么Student和Person都映射在一张表上,表中有一个discriminator列,这个列帮我们判断表中的记录示Student还是Person

2.TPT(table per type):为每一个领域类都创建一张单独的表

3.TPC(table per concrete class):为一个具体类创建一张表,抽象类不创建表。这种模式下,如果多个类都继承于一个抽象类,那么每个具体类都会包含抽象类的属性。

1.TPH(table per hierarchy) 每个继承体系映射一张表

我们先介绍TPH,首先添加领域类和上下文,注意上下文中我只设置了DbSet<BillingDetail>属性,没有为实现类(如BankAccount添加DbSet属性),如图

EF CodeFirst系列(3)---EF中的继承策略(暂存)

 //抽象账单类
    public abstract class BillingDetail
    {
        public int BillingDetailId { get; set; }//账单Id
        public string Owner { get; set; }//账单所有者
        public string Number { get; set; }//账单编号
    }

   //银行账单
    public class BankAccount:BillingDetail
    {
        public string BankName { get; set; }//银行名
        public string Swift { get; set; }//银行所属组织

    }

   //信用卡账单
   public class CreditCard:BillingDetail
    {
        public int CardType { get; set; }//信用卡类型
        public string ExpiryMonth { get; set; }//到期月份
        public string ExpiryYear { get; set; }//到期年份
    }

   //上下文
    public class InheritanceMappingContext:DbContext
    {
        public DbSet<BillingDetail> BillingDetails { get; set; }
    }

main函数中代码如下:

    class Program
    {
        static void Main(string[] args)
        {
            using (InheritanceMappingContext context=new InheritanceMappingContext())
            {
                context.BillingDetails.Add(,BankName="建设银行" });
                context.SaveChanges();
            }
        }
    }

运行程序可以看到结果如下所示:数据中建的表名对应父类名的复数: BillingDetails,一张表中包含了父类和子类的每所有属性。同时还有一个discriminator列,列的值是具体类的类名,本例中类名为BankAccount,这一列用于表示记录属于哪一个领域类。

EF CodeFirst系列(3)---EF中的继承策略(暂存)

当我们执行查询第一条账单时,返回的类型是抽象类,但是内部是BankAccount类型,没有信用卡类型的字段,如下图:

EF CodeFirst系列(3)---EF中的继承策略(暂存)

这时有一个问题,我们进行查询时会把所有的子类都查询出来,有没有办法只查询一种具体类型,如只查询信用卡账单的记录?通过OfType<T>可以帮我们解决这个问题:

 BillingDetail firstBilling = context.BillingDetails.OfType<CreditCard>().FirstOrDefault();

这种映射继承的策略简单且性能高,同时能很好地表达多态,推荐使用!

注意:

① 在TPH中每个子类特有的属性必须可空,因为其他子类可能没有当前子类的属性。如BankAccount有BankName属性,而CreditCard没有

② 这种策略违反了第三范式(第三范式:每个属性都和主键直接相关。BankAccount的记录也有CreditCard的CardType属性,但是CardType和BankAccount的主键不直接相关,虽然Discriminator的值能确定那些列属于BankAccount,但是Discriminator不是主键的一部分)

多态下的EF发送给ADO.NET的SQL:

SELECT
[Extent1].[Discriminator] AS [Discriminator],
[Extent1].[BillingDetailId] AS [BillingDetailId],
[Extent1].[Owner] AS [Owner],
[Extent1].[Number] AS [Number],
[Extent1].[BankName] AS [BankName],
[Extent1].[Swift] AS [Swift],
[Extent1].[CardType] AS [CardType],
[Extent1].[ExpiryMonth] AS [ExpiryMonth],
[Extent1].[ExpiryYear] AS [ExpiryYear]
FROM [dbo].[BillingDetails] AS [Extent1]
WHERE [Extent1].[Discriminator] IN ('BankAccount','CreditCard')

非多态下(OfType过滤)下发送给ADO.NET的Sql

SELECT
[Extent1].[BillingDetailId] AS [BillingDetailId],
[Extent1].[Owner] AS [Owner],
[Extent1].[Number] AS [Number],
[Extent1].[BankName] AS [BankName],
[Extent1].[Swift] AS [Swift]
FROM [dbo].[BillingDetails] AS [Extent1]
WHERE [Extent1].[Discriminator] = 'BankAccount'

通过FluentApi改变鉴别器类的数据类型和值(这一点可以在介绍完FluentApi后看)

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Entity<BillingDetail>()
                .Map<BankAccount>(m => m.Requires("BillingDetailType").HasValue("BA"))
                .Map<CreditCard>(m => m.Requires("BillingDetailType").HasValue("CC"));
}

2.TPT(table per type) 每种类型映射一张表

3.TPC(table per concrete class)每个具体类一张表