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 {View Codepublic string Id { get; set; } public DateTime AddTime { get; set; } }
學生類
public class Student:BaseEntity { public string Name { get; set; } public string Number { get; set; } }View Code
老師類
public classView CodeTeacher:BaseEntity { public string Name { get; set; } public decimal Salary { get; set; } }
注意:在上下文中我們必須要對基類公開一個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。”