咱們初始化數據庫一節已經知道: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):爲一個具體類建立一張表,抽象類不建立表。這種模式下,若是多個類都繼承於一個抽象類,那麼每一個具體類都會包含抽象類的屬性。性能
咱們先介紹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")); }