按持久化:继承中描述构建继承类:
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; }
EF默认的策略,只需要声明全部父子类的DbSet属性即可:
public DbSet<Person> Persons { get; set; } public DbSet<Student> Students { get; set; } public DbSet<Teacher> Teachers { get; set; }
演示:
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);
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)
EF core 5.0开始支持。
首先先声明父类的DbSet属性,然后在OnModelCreating()中指定子类映射的表:
modelBuilder.Entity<Student>().ToTable(nameof(Student)); modelBuilder.Entity<Teacher>().ToTable(nameof(Teacher));
注意生成的表结构:(演示)
而获取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
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);
只要父类没有被纳入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; }
这样,只会:
但父类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{}
我们统一使用Student和Teacher进行演示,先讲三种关系(1:1、1:n、n:n)的映射,再讲他们各自的增/删/加载……
首先,被entity关联的entity,会被自动映射。即可以不在DbContext中声明为DbSet属性,或OnModelCreating()中Entity<T>()。(演示)
演示映射的表结构:
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,自增),一个为“辅”(外键引用,不自增,值由所引用列确定)。
在EF的entity中,你可能会看到这样的写法:
public class Student : Person { public int TeacherId { get; set; } public Teacher Teacher { get; set; }
(之前没有被显式声明的)TeacherId就是所谓的“影子属性”:数据库的表上有(会自动映射)这么一列,但是C#的entity中可以不用显式声明的属性(官方定义)
大部分时候,这种影子属性可以省略,但微软推荐显式声明!
需要在OnModelCreating()中配置:
modelBuilder.Entity<Student>() .OwnsOne(s=>s.Bed)
演示:Bed不再被映射成一张表,而是Student表中的两列。
当一个Entity引用了其他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章节中数据持久化
多快好省!前端后端,线上线下,名师精讲
更多了解 加: