EF Core – 继承 Inheritance
前言
继承是面向对象里的概念. 关系数据库只有一对一, 一对多这类关系, 并没有 "继承" 关系的概念. 所以 ORM (Object–relational mapping) 就需要搞一些规则来映射出它.
这篇就说说 EF Core 如果映射继承关系.
题外话: 继承不顺风水, 尽量少用, 我的从前的经验是 OData 配 Entity Framework 6.0 继承经常会出 Bug. 就不知道这么多年后的 EF Core 有没有改善.
参考
How to configure inheritance mappings in Entity Framework 7
TPH (Table-per-hierarchy)
映射不只有一种方式, 目前一共有 3 种. 第一种叫 TPH (Table-per-hierarchy)
它有几个特色
1. 数据库只用一个表
2. 派生类的属性映射的 column 一定要是 nullable
优点是 query 速度快, 缺点是数据库结构有点乱, 因为它把全部东西都塞进一个表, 又那么多 nullable.
Model
假设有 4 个类
Cat, Dog, Fish ≼ Animal
public abstract class Animal
{
public int Id { get; set; }
public string SharedField { get; set; } = "";
} public class Cat : Animal
{
public string CatField { get; set; } = "";
}
public class Dog : Animal
{
public string DogField { get; set; } = "";
}
public class Fish : Animal
{
public string FishField { get; set; } = "";
}
注: Animal 是 abstract 类
Fluent API
public class ApplicationDbContext : DbContext
{
public DbSet<Animal> Animals => Set<Animal>();
public DbSet<Cat> Cat => Set<Cat>();
public DbSet<Dog> Dogs => Set<Dog>();
public DbSet<Fish> Fishes => Set<Fish>();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Animal>().UseTphMappingStrategy();
}
}
在 base class 声明 UseTphMappingStrategy 就可以了.
Database
数据库长这样.
只有一个表, 派生类的属性 Cat, Dog, Fish Field 都强制 nullable

Discriminator column 用来记入它们 under 什么类型.
进了数据后长这样

Column Name 的规则
by default 属性名会直接映射 column name. EF 不会加任何 prefix. 但如果不巧, 有一些派生类有相同属性 (但又不是全部派生类都有)
那么 EF 会加入 prefix
public class Dog : Animal
{
public string Name { get; set; } = "";
public string LastName { get; set; } = "";
}
public class Fish : Animal
{
public int Age { get; set; }
public string LastName { get; set; } = "";
}
Name, Age 没有 prefix, Fish.LastName 则会加入 prefix

注: 它不是两个都加 prefix, 而是只加其中一个...如果我们统一的话就需要自己配置了 (比如 set column name)
Shared Column
如果碰巧 LastName 的类型是一样的. 那么我们可以搞一个 shared column, 让它们共享一个 LastName column. 具体做法是 set 相同的 column name.
modelBuilder.Entity<Animal>().UseTphMappingStrategy();
modelBuilder.Entity<Dog>().Property(e => e.LastName).HasColumnName("LastName");
modelBuilder.Entity<Fish>().Property(e => e.LastName).HasColumnName("LastName");
效果

一些 Configuration
上面我都用了 default 的命名. EF 允许我们做一些配置.
public abstract class Animal
{
public int Id { get; set; }
public string SharedField { get; set; } = "";
public string Type { get; set; } = ""; // 加入一个 Type 替代默认的 Discriminator
}
配置
modelBuilder.Entity<Animal>().UseTphMappingStrategy()
.HasDiscriminator(e => e.Type)
.HasValue<Cat>("MyCat")
.HasValue<Dog>("MyDog")
.HasValue<Fish>("MyFish"); modelBuilder.Entity<Animal>().Property(e => e.Type).HasMaxLength(256);
这样我们就完全控制了 Discriminator.
TPT (Table-per-type)
第二种映射方式是 TPT (Table-per-type)
它的特色是表多.
不管是 base class 还是派生类, 统统建一个表.
优点是数据库结构好看, 不像 TPH 全部塞一个表, 又一堆 nullable. 缺点就是 query 慢. 因为每次 query 都需要 join.
就是因为有许多的 trade-off 所以 EF 才搞了 3 种 pattern 做映射. 你可以依据你的需求找出比较合适的 pattern. 死死用其中一个是不恰当的哦.
Fluen API
modelBuilder.Entity<Animal>().UseTptMappingStrategy();
Database
4 个类就 4 个表

每个表负责自己的 column

Id and relation 规则
join 表是靠相同 Id 完成的. 只有 base table 的 Id 是 auto increment. 其余的是 insert 时填进去的.
insert 过程是这样的.
无论 insert Dog, Fish, Cat. 首先 insert Animal 然后拿 Animal Id 去 insert 子表.
所以它不只是 query 慢, insert 也慢...这个映射 pattern 就是结构好. 但性能烂到家.
TPC (Table-per-concrete-type)
TPC 是 EF 7.0 才推出的. 它位于 TPH 和 TPT 的中间. 在性能和结构上又做了一些 balance.
它的特色是
抽象类不建表, 每一个子类各一个表.
抽象类的属性, 每一个子表都有相同的 column.
比快, 大部分情况下比不过 TPH, 但如果你单独 query 某一个类的数据, 它会比 TPH 还快.
比结构, 至少比 TPH 全部塞一表好, 而且也不需要 nullable. 比 TPT 差了一点点.
三表的速度比拼. 参考: Entity Framework 7 TPH, TPT and TPC inheritance mapping performance benchmarks

TPT 完全没有优势...哈哈
Fluent API
modelBuilder.Entity<Animal>().UseTpcMappingStrategy();
Id and relation 规则
TPT 利用 base table 来统一 Id, TPC 没了 base table 它怎么管理呢?
答案是 Sequence

Database

没有了 Animals 表, SharedField column 则每一个表都有.
总结
EF 一共给了 3 种方式来映射继承关系.
TPH
- 最 popular
- 绝大部分情况最快
- 全部属性塞一个表, 表臃肿
- 一堆 nullable 很烦人
TPT
- 结构最贴近 class, 每一个 class 都映射一个表
- 表干净, 没有 nullable, 也没有其它 class 的属性
- 速度慢, query 时要 join 表, insert 时要先 insert base table 拿到 id 后才 insert 子类的表
TPC
- 表干净, 没有 nullable, 也没有其它 class 的属性
- 速度还不错, query all 需要 join 表, 但 query one type 连 where 都不需要, 比 TPH 还快. insert 时不需要像 TPT insert 2 次, 所以比 TPT 快.
怎么选
首选 TPH. 除非 nullable 给了你很大的困扰, 或者 column 真的太多太臃肿了可以考虑换成 TPC
如果你确信业务需求通常都是指定 query 某个类, 那么可以优先考虑 TPC.
TPT 就等着被淘汰吧.
当 1-n 遇上 Inheritance
1-n relation 是这样的, 一个 Person 有 n 个 Animals
public class Person
{
public int Id { get; set; }
public List<Animal> Animals { get; set; } = new();
} public abstract class Animal
{
public int Id { get; set; }
public Person Person { get; set; } = null!;
public int PersonId { get; set; }
public string SharedField { get; set; } = "";
} public class Cat : Animal
{
public string CatField { get; set; } = "";
}
public class Dog : Animal
{
public string DogField { get; set; } = ""; }
public class Fish : Animal
{
public string FishField { get; set; } = "";
}
Relation Config
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Animal>().HasOne(e => e.Person).WithMany(e => e.Animals).HasForeignKey(e => e.PersonId).OnDelete(DeleteBehavior.Cascade);
}
如果我希望拆分成个别具体类型可以吗? 行!
public class Person
{
public int Id { get; set; }
// public List<Animal> Animals { get; set; } = new();
public List<Dog> Dogs { get; set; } = new();
public List<Cat> Cats { get; set; } = new();
public List<Fish> Fishes { get; set; } = new();
}
Relation Config
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Dog>().HasOne(e => e.Person).WithMany(e => e.Dogs).HasForeignKey(e => e.PersonId).OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<Cat>().HasOne(e => e.Person).WithMany(e => e.Cats).HasForeignKey(e => e.PersonId).OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<Fish>().HasOne(e => e.Person).WithMany(e => e.Fishes).HasForeignKey(e => e.PersonId).OnDelete(DeleteBehavior.Cascade);
// modelBuilder.Entity<Animal>().HasOne(e => e.Person).WithMany(e => e.Animals).HasForeignKey(e => e.PersonId).OnDelete(DeleteBehavior.Cascade); // 一定要在下面
}
这样子是 ok 的, 但是不可以抽象具体一起 (把上面注释的打开, 就不行了)
一旦抽象具体一起, 数据库就会变成多个 PersonId column. 哪怕我们用 shared column 也无发让它们变成一个 column.
我觉得是它目前支持的不好, 以后或许是可以的.
EF Core – 继承 Inheritance的更多相关文章
- EntityFramework Core技术线路(EF7已经更名为EF Core,并于2016年6月底发布)
官方文档英文地址:https://github.com/aspnet/EntityFramework/wiki/Roadmap 历经延期和更名,新版本的实体框架终于要和大家见面了,虽然还有点害羞.请大 ...
- [转]EntityFramework Core技术线路(EF7已经更名为EF Core,并于2016年6月底发布)
本文转自:http://www.cnblogs.com/VolcanoCloud/p/5572408.html 官方文档英文地址:https://github.com/aspnet/EntityFra ...
- 一步步学习EF Core(2.事务与日志)
前言 上节我们留了一个问题,为什么EF Core中,我们加载班级,数据并不会出来 其实答案很简单,~ 因为在EF Core1.1.2 中我们在EF6.0+中用到的的延迟加载功能并没有被加入,不过在EF ...
- 一步步学习EF Core(3.EF Core2.0路线图)
前言 这几天一直在研究EF Core的官方文档,暂时没有发现什么比较新的和EF6.x差距比较大的东西. 不过我倒是发现了EF Core的路线图更新了,下面我们就来看看 今天我们来看看最新的EF Cor ...
- AspNet Core :创建自定义 EF Core 链接数据库
这两天比较忙,写的会慢一点. 我们以控制台演示 EF Core的链接数据库 首先创建控制台程序 创建数据上下文类 EntityTable /// <summary> /// 继承 DbCo ...
- .net core Entity Framework 与 EF Core
重点讲 Entity Framework Core ! (一)Entity Framework 它是适用于.NET 的对象关系映射程序 (ORM),现在的EF6已经是久经沙场,并经历重重磨难,获得一致 ...
- [翻译 EF Core in Action 2.2] 创建应用程序的数据库上下文
Entity Framework Core in Action Entityframework Core in action是 Jon P smith 所著的关于Entityframework Cor ...
- [翻译 EF Core in Action 2.1] 设置一个图书销售网站的场景
Entity Framework Core in Action Entityframework Core in action是 Jon P smith 所著的关于Entityframework Cor ...
- [翻译 EF Core in Action 1.8] MyFirstEfCoreApp应用程序设置
Entity Framework Core in Action Entityframework Core in action是 Jon P smith 所著的关于Entityframework Cor ...
- EF Core 快速上手——创建应用的DbContext
系列文章 EF Core 快速上手--EF Core 入门 EF Core 快速上手--EF Core的三种主要关系类型 本节导航 定义应用的DbContext 创建DbContext的一个实例 创建 ...
随机推荐
- 高程读后感(三)— JS对象实现继承的6种模式及其优缺点
目录 1.原型链 1.1.默认的原型 1.2.原型和实例的关系 1.3.原型链的问题 2.借用构造函数 2.1.传递参数 2.2.借用构造函数的问题 3.组合继承 4.原型式继承 5.寄生式继承 6. ...
- [oeasy]python0074_修改字体背景颜色_background_color_背景色
修改背景色 回忆上次内容 上次将asciiart和颜色一起来玩 7 种基本色 变化多端 不过到目前为止 改的 都是前景色 背景色可以修改吗? 重温参数 具体动手试试 print("\033[ ...
- oeasy教您玩转vim - 19 - 使用标记
使用标记 回忆上节课内容 跳转到行号 200G 设置行号选项 显示行号 :se nu 显示相对行号 :se rnu 如何用命令行跳转 :100 但是我如何有的时候记不住到底跳到多少行 能否做个标记留个 ...
- [oeasy]教您玩转python - 0003 - 编写 py 文件
编写 py 文件 回忆上次内容 次在解释器里玩耍 了解到字符串就是给一堆字符两边加引号 可以是单引号 也可以是双引号 这样游乐场就知道 这个不是一个名字 而是一个字符串 字符串可以用print函数 ...
- 云原生 .NET Aspire 8.1 新增对 构建容器、编排 Python的支持
.NET Aspire 用于云原生应用开发,提供用于构建.测试和部署分布式应用的框架,这些应用通常利用微服务.容器.无服务器体系结构等云构造.2024年7月23日宣布的新 8.1 版本是该平台自 5 ...
- 如何使用深度学习技术探测代码逻辑死循环 —— 浪潮集团的“公开号CN117271314A”专利
专利公开号: CN117271314A 新闻链接: https://mbd.baidu.com/newspage/data/landingsuper?context={"nid"% ...
- 中国超级计算机为什么不能为AI提供算力?
网上看到这样的帖子: https://www.zhihu.com/question/609008408/answer/3130831897 ============================== ...
- GOT & PLT 易于理解的个人笔记
为什么我们用动态链接和GOT表 我们知道静态链接就没那么多事,直接把全部要用的函数都绑定在一起,各个变量和函数之间的偏移量当然能算出来. 但是这也恰恰是静态链接的缺点,相同的代码段反复调用真是太臃肿了 ...
- 【A GUIDE TO CRC ERROR DETECTION ALGORITHM】 (译文2)
6. A Fully Worked Example 一个完全可行的例子 Having defined CRC arithmetic, we can now frame a CRC calculatio ...
- Camera | 7.瑞芯微rk3568平台摄像头控制器MIPI-CSI驱动架构梳理
因为有拍照.录制视频.直播等刚需,现在手机的摄像头基本都是高清,支持高清摄像头的SoC都支持MIPI-CSI. 不同SoC的MIPI-CSI在实现上有一定差别,即使同一厂家设计生产的芯片也都不尽相同. ...