1. 程式人生 > >EF6學習筆記五:繼承三策略

EF6學習筆記五:繼承三策略

要專業系統地學習EF前往《你必須掌握的Entity Framework 6.x與Core 2.0》這本書的作者(汪鵬,Jeffcky)的部落格:https://www.cnblogs.com/CreateMyself/

Table Per Hierarchy(TPH)

Table Per Type(TPT)

Table Per Concrete class(TPC)

我弄一下一,發現這些東西我幾乎沒用過上幾篇我寫了BaseEntity,但按照我學的這些東西來看,這個不叫繼承

TPH

程式碼一貼你就能知道怎麼回事

基類BaseEntity

public class BaseEntity
    {
        
public string Id { get; set; } public DateTime AddTime { get; set; } }
View Code

 

學生類

public class Student:BaseEntity
    {
        public string Name { get; set; }
        public string Number { get; set; }
    }
View Code

 

老師類

public class
Teacher:BaseEntity { public string Name { get; set; } public decimal Salary { get; set; } }
View Code

 

注意:在上下文中我們必須要對基類公開一個DbSet<>屬性

public DbSet<BaseEntity> BaseEntities { get; set; }
View Code

 

生成表結構如下

就是這樣,兩個子類的屬性全部在基類中,而且系統新新增一個欄位“Discriminator”來區分不同的子類,而且屬於子類的屬性必須是非空

我們看一下插入、查詢資料怎麼做

using (EFDbContext db = new EFDbContext())
            {
                //  新增一個學生
                db.BaseEntities.Add(new Student
                {
                    Id = Guid.NewGuid().ToString(),
                    AddTime = DateTime.Now,
                    Name = "張三",
                    Number = "number001"
                });
                db.SaveChanges();

                //  查詢所有學生,用ofType方法
                var res = JsonConvert.SerializeObject(db.BaseEntities.OfType<Student>().ToList());
                Console.WriteLine(res);
            }
View Code

 

我們可以對這種方式的繼承再做一點配置,換一種方式來代替“Discriminator”,也沒太大變化,只不過分別弄出兩個欄位,來辨別兩個實體

modelBuilder.Entity<BaseEntity>().Map<Student>(m =>
            {
                m.Requires("StudentType").HasValue(1);
            }).Map<Teacher>(m =>
            {
                m.Requires("TeacherType").HasValue(2);
            });
View Code

 

 然後我新增一個學生,一個老師,看看錶裡面是什麼情況

TPH就是這樣的,我也不知道這種什麼情況下使用,往下面看

TPT

基類Details

public class Details
    {
        public string DetailsId { get; set; }
        public string Decirtions { get; set; }
    }
View Code

 

圖書類

public class Book : Details
    {
        public string BookId { get; set; }
        public string Name { get; set; }
        public string Number { get; set; }
    }
View Code

 

水果類

public class Fruit:Details
    {
        public string FruitId { get; set; }
        public string Name { get; set; }
        public decimal Price { get; set; }
    }
View Code

 

然後配置的時候,為這三個model公開Dbset<>屬性,並且子類需要在onModelCreating中配置一下,不然就直接對映成TPH模式了

public DbSet<Details> Details { get; set; }
        public DbSet<Book> Books { get; set; }
        public DbSet<Fruit> Fruit { get; set; }
View Code
modelBuilder.Entity<Details>().ToTable("tb_Details");
            modelBuilder.Entity<Book>().ToTable("tb_Books");
            modelBuilder.Entity<Fruit>().ToTable("tb_Fruits");
View Code

 

生成的表結構如下

我新增一個水果,他會預設在details表中新增一條記錄

作者說這種方式用的最多,但是效能不是很好

比如我們查詢所有的水果,生成的SQL如下

SELECT
    '0X0X' AS [C1],
    [Extent1].[DetailsId] AS [DetailsId],
    [Extent1].[Decirtions] AS [Decirtions],
    [Extent2].[FruitId] AS [FruitId],
    [Extent2].[Name] AS [Name],
    [Extent2].[Price] AS [Price]
    FROM  [dbo].[tb_Details] AS [Extent1]
    INNER JOIN [dbo].[tb_Fruits] AS [Extent2] ON [Extent1].[DetailsId] = [Extent2].[DetailsId]
View Code

我們查詢基類,生成的SQL是這樣的,他會連線查詢所有的子表

SELECT
    CASE WHEN (( NOT (([Project2].[C1] = 1) AND ([Project2].[C1] IS NOT NULL))) AND ( NOT (([Project1].[C1] = 1) AND ([Project1].[C1] IS NOT NULL)))) THEN '0X' WHEN (([Project2].[C1] = 1) AND ([Project2].[C1] IS NOT NULL)) THEN '0X0X' ELSE '0X1X' END AS [C1],
    [Extent1].[DetailsId] AS [DetailsId],
    [Extent1].[Decirtions] AS [Decirtions],
    CASE WHEN (( NOT (([Project2].[C1] = 1) AND ([Project2].[C1] IS NOT NULL))) AND ( NOT (([Project1].[C1] = 1) AND ([Project1].[C1] IS NOT NULL)))) THEN CAST(NULL AS varchar(1)) WHEN (([Project2].[C1] = 1) AND ([Project2].[C1] IS NOT NULL)) THEN [Project2].[BookId] END AS [C2],
    CASE WHEN (( NOT (([Project2].[C1] = 1) AND ([Project2].[C1] IS NOT NULL))) AND ( NOT (([Project1].[C1] = 1) AND ([Project1].[C1] IS NOT NULL)))) THEN CAST(NULL AS varchar(1)) WHEN (([Project2].[C1] = 1) AND ([Project2].[C1] IS NOT NULL)) THEN [Project2].[Name] END AS [C3],
    CASE WHEN (( NOT (([Project2].[C1] = 1) AND ([Project2].[C1] IS NOT NULL))) AND ( NOT (([Project1].[C1] = 1) AND ([Project1].[C1] IS NOT NULL)))) THEN CAST(NULL AS varchar(1)) WHEN (([Project2].[C1] = 1) AND ([Project2].[C1] IS NOT NULL)) THEN [Project2].[Number] END AS [C4],
    CASE WHEN (( NOT (([Project2].[C1] = 1) AND ([Project2].[C1] IS NOT NULL))) AND ( NOT (([Project1].[C1] = 1) AND ([Project1].[C1] IS NOT NULL)))) THEN CAST(NULL AS varchar(1)) WHEN (([Project2].[C1] = 1) AND ([Project2].[C1] IS NOT NULL)) THEN CAST(NULL AS varchar(1)) ELSE [Project1].[FruitId] END AS [C5],
    CASE WHEN (( NOT (([Project2].[C1] = 1) AND ([Project2].[C1] IS NOT NULL))) AND ( NOT (([Project1].[C1] = 1) AND ([Project1].[C1] IS NOT NULL)))) THEN CAST(NULL AS varchar(1)) WHEN (([Project2].[C1] = 1) AND ([Project2].[C1] IS NOT NULL)) THEN CAST(NULL AS varchar(1)) ELSE [Project1].[Name] END AS [C6],
    CASE WHEN (( NOT (([Project2].[C1] = 1) AND ([Project2].[C1] IS NOT NULL))) AND ( NOT (([Project1].[C1] = 1) AND ([Project1].[C1] IS NOT NULL)))) THEN CAST(NULL AS decimal(18,2)) WHEN (([Project2].[C1] = 1) AND ([Project2].[C1] IS NOT NULL)) THEN CAST(NULL AS decimal(18,2)) ELSE [Project1].[Price] END AS [C7]
    FROM   [dbo].[tb_Details] AS [Extent1]
    LEFT OUTER JOIN  (SELECT
        [Extent2].[DetailsId] AS [DetailsId],
        [Extent2].[FruitId] AS [FruitId],
        [Extent2].[Name] AS [Name],
        [Extent2].[Price] AS [Price],
        cast(1 as bit) AS [C1]
        FROM [dbo].[tb_Fruits] AS [Extent2] ) AS [Project1] ON [Extent1].[DetailsId] = [Project1].[DetailsId]
    LEFT OUTER JOIN  (SELECT
        [Extent3].[DetailsId] AS [DetailsId],
        [Extent3].[BookId] AS [BookId],
        [Extent3].[Name] AS [Name],
        [Extent3].[Number] AS [Number],
        cast(1 as bit) AS [C1]
        FROM [dbo].[tb_Books] AS [Extent3] ) AS [Project2] ON [Extent1].[DetailsId] = [Project2].[DetailsId]
View Code

 

 TPT的缺點就在於效能差,當然你不能什麼都用這樣方式,每種方式都有自己特有的使用情境

TPC

 基類

public class Base
    {
        public string Id { get; set; }
        public string Dd { get; set; }
    }
View Code

 

 子類1

public class Child1:Base
    {
        public string Name { get; set; }
        public string Ee { get; set; }
    }
View Code

 

子類2

public class Child2:Base
    {
        public string Name { get; set; }
        public string Dcv { get; set; }
    }
View Code

 

配置

//  TPC
            modelBuilder.Entity<Child1>().Map(m =>
            {
                m.MapInheritedProperties();
                m.ToTable("tb_Child1s");
            });
            modelBuilder.Entity<Child2>().Map(m =>
            {
                m.MapInheritedProperties();
                m.ToTable("tb_Child2s");
            });
View Code

 

 生成的表結構如下

這三種繼承策略對我來說,我覺得不用不到,現在也有些亂

最後引用作者的一段話做個總結

“對於單一適用所有場景的對映繼承策略不存在,上述每種策略都有其優缺點。如果不需要多表關聯或查詢,從不或者很少查詢基類並且沒有與基類關聯的類,推薦使用TPC;

如果需要多表關聯或查詢,並且子類中有較少的屬性(特別是子類之間需要進行區別),推薦使用TPH(TPH實現推薦使用自定義Disciminator);

如果需要多表關聯或查詢,並且子類宣告許多屬性(子類主要取決於它們所持有的資料),推薦使用TPT。”