键盘敲烂,月薪过万作业不做,等于没学
当前系列: ADO&EF 修改讲义

继承

持久化:继承中描述构建继承类:

public class Person
{
    public int Id { get; set; }
    public string Name { get; set; }
    public class Student : Person
    {
        public double Score { get; set; }
    public class Teacher : Person
    {
        public double Salary { get; set; }

Table-Per-Hierarchy (TPH)

EF默认的策略,只需要声明全部父子类的DbSet属性即可:

public DbSet<Person> Persons { get; set; }
public DbSet<Student> Students { get; set; }
public DbSet<Teacher> Teachers { get; set; }

演示:

  1. 生成的表结构:Discriminator
  2. 存入数据,Discriminator的值对应entity的类名。
    Person xj = new Person
    {
        Name = "小九"
    };
    
    Student atai = new Student
    {
        Name = "atai",
        Score = 90
    };
    
    Teacher fg = new Teacher
    {
        Name = "飞哥",
        Salary = 8000
    };
    
    context.AddRange(xj, fg, atai);
  3. 读取数据:SQL查询时,会加入Discriminator的条件
    SELECT TOP(1) [p].[Id], [p].[Discriminator], [p].[Name], [p].[Score]
    FROM [Persons] AS [p]
    WHERE ([p].[Discriminator] = N'Student') AND ([p].[Id] = @__p_0)

Table-Per-Type(TPT)

EF core 5.0开始支持。

首先先声明父类的DbSet属性,然后在OnModelCreating()中指定子类映射的表:

modelBuilder.Entity<Student>().ToTable(nameof(Student));
modelBuilder.Entity<Teacher>().ToTable(nameof(Teacher));

注意生成的表结构:(演示)

  • 基类的Id才是IDENTITY的
  • 子类的Id不能IDENTITY,因为它的值只能通过基类INSERT之后SELECT获得,所以还有和基类Id的外键关系。

而获取entity时,如果指定获取的是:

  • 子类entity
    context.Students.Find(3)
    会INNER JOIN基类表
          SELECT TOP(1) [p].[Id], [p].[Name], [s].[Score]
          FROM [Persons] AS [p]
          INNER JOIN [Student] AS [s] ON [p].[Id] = [s].[Id]
          WHERE [p].[Id] = @__p_0
  • 基类entity
    context.Persons.Find(3)
    会LEFT JOIN所有子类表:
          SELECT TOP(1) [p].[Id], [p].[Name], [s].[Score], [t].[Salary], CASE
              WHEN [t].[Id] IS NOT NULL THEN N'Teacher'
              WHEN [s].[Id] IS NOT NULL THEN N'Student'
          END AS [Discriminator]
          FROM [Persons] AS [p]
          LEFT JOIN [Student] AS [s] ON [p].[Id] = [s].[Id]
          LEFT JOIN [Teacher] AS [t] ON [p].[Id] = [t].[Id]
          WHERE [p].[Id] = @__p_0
    还使用了Discriminator确认其实际类型,鸡贼!^_^

@想一想@:为什么要这么麻烦?怕你类型强转呀!

Console.WriteLine(((Student)person).Score);

Table-Per-Concrete-Type(TPC)

只要父类没有被纳入Model/视为entity管理即可。

比如,这样的DbContext,Person就没有被纳入Model/视为entity:

public class SqlDbContext : DbContext
{
    //public DbSet<Person> Persons { get; set; }

    public DbSet<Student> Students { get; set; }
    public DbSet<Teacher> Teachers { get; set; }

这样,只会:

  • 生成Students和Teachers两张表,
  • 他们继承的父类属性(Id和Name)会被映射到子类列,

但父类Person本身不会被映射到表——这就可以被视为TPC策略的实现。

但这样的代码会报错:

Person person = new Person
{
    Name = "PERSON"
};
context.Add(person);
context.SaveChanges();

The entity type 'Person' was not found.

所以没有被映射的父类最好是abstract的,比如这样:

public abstract class Person{}


关联映射

复习:ER模型/名词解释/外键实现……

我们统一使用StudentTeacher进行演示,先讲三种关系(1:1、1:n、n:n)的映射,再讲他们各自的增/删/加载…… 

首先,被entity关联的entity,会被自动映射。即可以不在DbContext中声明为DbSet属性,或OnModelCreating()中Entity<T>()。演示

演示映射的表结构:

  • 一对多:单向/双向引用,均只会在“n端”生成一个外键列
  • 多对多:只能双向引用,EF自动生成一个关系表
  • 一对一:必须双向且指定外键关系:
    modelBuilder.Entity<Student>()
        .HasOne(s => s.Teacher)
        .WithOne(t => t.Student)
        //以上可简单翻译成:
        //Student有一个Teacher,而且Teacher只有一个Student
        //在Student的Id上设置外键引用
        .HasForeignKey<Student>(s => s.Id)
        //或者,将Student的Id设置为“主”键
        .HasPrincipalKey<Student>(s => s.Id)
        ;
    Student.Id和Teacher.Id,必有一个为“主”(Principal,自增),一个为“辅”(外键引用,不自增,值由所引用列确定)。
    注意:这时候entity中只能有影子属性(详见下文)

影子属性

在EF的entity中,你可能会看到这样的写法:

public class Student : Person
{
    public int TeacherId { get; set; }
    public Teacher Teacher { get; set; }

(之前没有被显式声明的)TeacherId就是所谓的“影子属性”:数据库的表上有(会自动映射)这么一列,但是C#的entity中可以不用显式声明的属性(官方定义

大部分时候,这种影子属性可以省略,但微软推荐显式声明!


Owned Entity

复习:Entity中一些类,并不需要被映射成单独的表。……

需要在OnModelCreating()中配置:

modelBuilder.Entity<Student>()
    .OwnsOne(s=>s.Bed)

演示:Bed不再被映射成一张表,而是Student表中的两列。


持久化存入

当一个Entity引用了其他Entity时,在持久化该Entity时,会自动把:

  • 所关联的所有Entity
  • 以及关联Enity的关联Entity
  • 以及关联Enity的关联Entity的关联Entity
  • ……

以及他们的关系,一起持久化(增删改)到数据库。

一对多

无论是单向引用,

Teacher fg = new Teacher
{
    Name = "飞哥",
    Salary = 8000,
    Student = new Student
    {
        Name = "atai",
        Score = 90,
context.Teachers.Add(fg);

还是双向引用,通过集合添加:

atai.Teachers = new List<Teacher>();
atai.Teachers.Add(fg);
//不需要:fg.Student = atai; context.Students.Add(atai);

EF都能够按顺序正确的插入两条数据。

多对多

偷个懒,单边的建立关系,都可以正确的映射:

atai.Teachers = new List<Teacher>
{
    fg,xj
};
context.Students.Add(atai);

一对一

单向引用仍然是生效的。

但是,注意Add()的应该是“关联的”(principle)、而不是“被关联的”(foreign)那个entity:

fg.Student = atai;
context.Teachers.Add(fg);  //正确
context.Students.Add(atai);//错

综合演示:将Linq章节中数据持久化

学习笔记
源栈学历
大多数人,都低估了编程学习的难度,而高估了自己的学习能力和毅力。

作业

觉得很 ,不要忘记分享哟!

任何问题,都可以直接加 QQ群:273534701

在当前系列 ADO&EF 中继续学习:

多快好省!前端后端,线上线下,名师精讲

  • 先学习,后付费;
  • 不满意,不要钱。
  • 编程培训班,我就选源栈

更多了解 加:

QQ群:273534701

答疑解惑,远程debug……

B站 源栈-小九 的直播间

写代码要保持微笑 (๑•̀ㅂ•́)و✧

公众号:源栈一起帮

二维码