要专业系统地学习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; }
}
学生类
public class Student:BaseEntity
{
public string Name { get; set; }
public string Number { get; set; }
}
老师类
public class Teacher:BaseEntity
{
public string Name { get; set; }
public decimal Salary { get; set; }
}
注意:在上下文中我们必须要对基类公开一个DbSet<>属性
public DbSet<BaseEntity> BaseEntities { get; set; }
生成表结构如下
就是这样,两个子类的属性全部在基类中,而且系统新添加一个字段“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);
}
我们可以对这种方式的继承再做一点配置,换一种方式来代替“Discriminator”,也没太大变化,只不过分别弄出两个字段,来辨别两个实体
modelBuilder.Entity<BaseEntity>().Map<Student>(m =>
{
m.Requires("StudentType").HasValue(1);
}).Map<Teacher>(m =>
{
m.Requires("TeacherType").HasValue(2);
});
然后我添加一个学生,一个老师,看看表里面是什么情况
TPH就是这样的,我也不知道这种什么情况下使用,往下面看
TPT
基类Details
public class Details
{
public string DetailsId { get; set; }
public string Decirtions { get; set; }
}
图书类
public class Book : Details
{
public string BookId { get; set; }
public string Name { get; set; }
public string Number { get; set; }
}
水果类
public class Fruit:Details
{
public string FruitId { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
然后配置的时候,为这三个model公开Dbset<>属性,并且子类需要在onModelCreating中配置一下,不然就直接映射成TPH模式了
public DbSet<Details> Details { get; set; }
public DbSet<Book> Books { get; set; }
public DbSet<Fruit> Fruit { get; set; }
modelBuilder.Entity<Details>().ToTable("tb_Details");
modelBuilder.Entity<Book>().ToTable("tb_Books");
modelBuilder.Entity<Fruit>().ToTable("tb_Fruits");
生成的表结构如下
我添加一个水果,他会默认在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]
我们查询基类,生成的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]
TPT的缺点就在于性能差,当然你不能什么都用这样方式,每种方式都有自己特有的使用情境
TPC
基类
public class Base
{
public string Id { get; set; }
public string Dd { get; set; }
}
子类1
public class Child1:Base
{
public string Name { get; set; }
public string Ee { get; set; }
}
子类2
public class Child2:Base
{
public string Name { get; set; }
public string Dcv { get; set; }
}
配置
// TPC
modelBuilder.Entity<Child1>().Map(m =>
{
m.MapInheritedProperties();
m.ToTable("tb_Child1s");
});
modelBuilder.Entity<Child2>().Map(m =>
{
m.MapInheritedProperties();
m.ToTable("tb_Child2s");
});
生成的表结构如下
这三种继承策略对我来说,我觉得不用不到,现在也有些乱
最后引用作者的一段话做个总结
“对于单一适用所有场景的映射继承策略不存在,上述每种策略都有其优缺点。如果不需要多表关联或查询,从不或者很少查询基类并且没有与基类关联的类,推荐使用TPC;
如果需要多表关联或查询,并且子类中有较少的属性(特别是子类之间需要进行区别),推荐使用TPH(TPH实现推荐使用自定义Disciminator);
如果需要多表关联或查询,并且子类声明许多属性(子类主要取决于它们所持有的数据),推荐使用TPT。”
来源:oschina
链接:https://my.oschina.net/u/4346770/blog/4263279