EF CodeFirst系列(3)---EF中的繼承策略(暫存)

  咱們初始化數據庫一節已經知道:EF爲每個具體的類生成了數據庫的表。如今有了一個問題:咱們在設計領域類時常常用到繼承,這能讓咱們的代碼更簡潔且容易管理,在面向對象中有「has  a」和「is a」關係(如student has a name,student is a person--繼承),然而數據庫中只有「has a」關係。數據庫管理系統並不支持繼承,因此咱們怎麼去映射具備繼承關係的領域類呢?數據庫

EF CodeFirst中有三種方式表示繼承體系:app

1.TPH(table per hierarchy): 這是EF的默認方式,這種方式把整個繼承體系都映射在一個表中,經過一個discriminator(鑑別器)列來判斷繼承關係。如Student繼承於Person類,那麼Student和Person都映射在一張表上,表中有一個discriminator列,這個列幫咱們判斷表中的記錄示Student仍是Personide

2.TPT(table per type):爲每個領域類都建立一張單獨的表函數

3.TPC(table per concrete class):爲一個具體類建立一張表,抽象類不建立表。這種模式下,若是多個類都繼承於一個抽象類,那麼每一個具體類都會包含抽象類的屬性。性能

1.TPH(table per hierarchy) 每一個繼承體系映射一張表

咱們先介紹TPH,首先添加領域類和上下文,注意上下文中我只設置了DbSet<BillingDetail>屬性,沒有爲實現類(如BankAccount添加DbSet屬性),如圖ui

 //抽象帳單類
    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函數中代碼以下:spa

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

運行程序能夠看到結果以下所示:數據中建的表名對應父類名的複數: BillingDetails,一張表中包含了父類和子類的每全部屬性。同時還有一個discriminator列,列的值是具體類的類名,本例中類名爲BankAccount,這一列用於表示記錄屬於哪個領域類。設計

當咱們執行查詢第一條帳單時,返回的類型是抽象類,可是內部是BankAccount類型,沒有信用卡類型的字段,以下圖:code

這時有一個問題,咱們進行查詢時會把全部的子類都查詢出來,有沒有辦法只查詢一種具體類型,如只查詢信用卡帳單的記錄?經過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)每一個具體類一張表

相關文章
相關標籤/搜索