EFCore:表之间映射关系与导航属性的解读
1、前置数据与环境准备
需要安装前置NuGet包:Pomelo.EntityFrameworkCore.Mysql 、microsoft.entityframeworkcore、Microsoft.EntityFrameworkCore.Tools
1.1设计如下实体类
//学生信息表 public class Student { public int StudentId { get; set; } public string Name { get; set; } public string Sex { get; set; } public int Age { get; set; } } //教师信息表 public class Teacher { public int TeacherId { get; set; } public string Name { get; set; } public string Title { get; set; } } //课程表 public class Course { public int CourseId { get; set; } public string Name { get; set; } public Teacher Teacher { get; set; } } //学生家庭地址表 public class StudentAddress { public int StudentAddressId { get; set; } public string Address { get; set; } public string City { get; set; } }
创建新的项目EFStudent,新建文件夹 Models,并依据上述实体类,创建对应的类模型。
1.2 确认Student、Teacher、Course、StudentAddress各表之间的关系
1.2.1 Student
一个学生只能有一个固定的家庭地址留存,所以学生和地址是一对一的关系。学生可以选择多个课程,学生和课程是多对多的关系。Student修改如下:
//学生信息表 public class Student { public int StudentId { get; set; } public string Name { get; set; } public string Sex { get; set; } public int Age { get; set; } //导航属性 //一对一 public StudentAddress Address { get; set; } //一对多 public IList<Course> Courses { get; set; } = new List<Course>(); }
1.2.2 Teacher
一个老师可以教授多门课程,Teacher修改如下:
//教师信息表 public class Teacher { public int TeacherId { get; set; } public string Name { get; set; } public string Title { get; set; } //一对多 public IList<Course> Courses { get; set; } = new List<Course>(); }
1.2.3 Course
一个学生可能选择多门课程,一个课程也对应多个学生,Course修改如下:
//课程表 public class Course { public int CourseId { get; set; } public string Name { get; set; } public Teacher Teacher { get; set; } //一对多 public IList<Student> Students { get; set; } = new List<Student>(); }
1.2.4 StudentAddress
一个学生只能有一个固定的家庭地址留存,一个地址对应一个学生,StudentAddress修改如下:
public class StudentAddress { public int StudentAddressId { get; set; } public string Address { get; set; } public string City { get; set; } //导航属性 public int StudentId { get; set; } public Student Student { get; set; } }
1.3 创建DbContext类(EfDbContext)并配置相关对应关系
public class EfDbContext : DbContext { public EfDbContext() { } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseMySql("server=b.lifeiai.com;user id=EFStudent;password=lf123456;database=efstudent", Microsoft.EntityFrameworkCore.ServerVersion.Parse("5.6.50-mysql")); } protected override void OnModelCreating(ModelBuilder modelBuilder) { //设置Id自增 modelBuilder.Entity<Student>().Property(x => x.StudentId).ValueGeneratedOnAdd(); //设置姓名最大长度为50,字符为unicode,不能为空 modelBuilder.Entity<Student>().Property(x => x.Name).HasMaxLength(50).IsUnicode().IsRequired(); //设置性别最大长度为5 字符为Unicode,不能为空 modelBuilder.Entity<Student>().Property(x => x.Sex).HasMaxLength(5).IsUnicode().IsRequired(); //一对一只需要配置一个类就行了 modelBuilder.Entity<Student>().HasOne(x => x.Address).WithOne(x => x.Student).HasForeignKey<StudentAddress>(ad => ad.StudentId); modelBuilder.Entity<Course>().Property(x => x.Name).HasMaxLength(50).IsUnicode(); //课程一对一 modelBuilder.Entity<Course>().HasOne(x => x.Teacher).WithMany(t => t.Courses); modelBuilder.Entity<Course>().HasMany(x => x.Students).WithMany(st => st.Courses); modelBuilder.Entity<Teacher>().Property(x => x.Name).HasMaxLength(50).IsUnicode(); modelBuilder.Entity<Teacher>().Property(x => x.Title).HasMaxLength(50).IsUnicode(); modelBuilder.Entity<StudentAddress>().Property(x => x.City).HasMaxLength(100).IsRequired().IsUnicode(); modelBuilder.Entity<StudentAddress>().Property(x => x.Address).HasMaxLength(500).IsRequired().IsUnicode(); } public DbSet<Student> Students { get; set; } public DbSet<Teacher> Teachers { get; set; } public DbSet<Course> Courses { get; set; } public DbSet<StudentAddress> Addresses { get; set; } }
根据以上代码可以总结一些注意事项,内容如下:
<1>配置一对一关系时,仅配置一方即可;
<2>配置一对多关系时,从多的方面下手,采用HasOne(…).WithMany(…)的方法比较简单和容易理解;
<3>传统的关系数据库中,配置多对多的时候,我们需要第三方的中间表进行相关联;EFCore6.0以上时,只需要配置相关的导航属性后,设置HasMany(…).WithMany(…)进行配置,EFCore自己生成相关的中间表;
<4>在传统的关系数据库中,配置多对多的时候,我们需要第三方的中间表进行相关联,但是在EFCore6.0以上时,我们只需要配置相关的导航属性后,设置HasMany(…).WithMany(…)进行配置,EFCore自己生成相关的中间表。
2.新增一些测试数据
using (var dbContext = new EfDbContext()) { Course course1 = new Course() { CourseId = 3001, Name = "高等数学" }; Course course2 = new Course() { CourseId = 3002, Name = "计算机原理" }; Course course3 = new Course() { CourseId = 3003, Name = "操作系统原理" }; Course course4 = new Course() { CourseId = 3004, Name = "编译原理" }; Teacher teacher1 = new Teacher() { TeacherId = 10001, Name = "张教授", Title = "教授" }; Teacher teacher2 = new Teacher() { TeacherId = 10002, Name = "王讲师", Title = "讲师" }; teacher1.Courses = new Course[] { course1, course2 }; teacher2.Courses = new Course[] { course3, course4 }; dbContext.Teachers.AddRange(teacher1, teacher2); StudentAddress Address1 = new StudentAddress() { StudentAddressId = 37001, Address = "北京朝阳区", City = "北京", }; StudentAddress Address2 = new StudentAddress() { StudentAddressId = 37002, Address = "上海徐汇区", City = "上海", }; StudentAddress Address3 = new StudentAddress() { StudentAddressId = 37003, Address = "广州白云区", City = "广州", }; Student student1 = new Student() { StudentId = 2022001, Name = "王二小", Age = 19, Sex = "男", Address = Address1, Courses = new Course[] { course1, course2, course3 }, }; Student student2 = new Student() { StudentId = 2022002, Name = "张小五", Age = 20, Sex = "男", Address = Address2, Courses = new Course[] { course1, course2 }, }; Student student3 = new Student() { StudentId = 2022003, Name = "刘小花", Age = 20, Sex = "女", Address = Address3, Courses = new Course[] { course1, course3 }, }; dbContext.Students.AddRange(student1, student2, student3); dbContext.SaveChanges(); }
上述代码部分解析:
AddRange()是批量添加,如上所示,在上面的例子中,我们只需要根据对应关系添加数据,在dbContext里添加对应关系的一方的就可以了,打开数据库后,可以发现数据顺利添加在其中。例如上面我们并没有调用dbContext.Addresses.Add()等方法,但是地址信息还是顺利写入了地址表。
2.1 查询数据
var dbContext = new EfDbContext(); var students= dbContext.Students.ToList(); foreach (var st in students) { Console.WriteLine($"StudentId:{st.StudentId},Name:{st.Name},City:{st.Address.City}, Address:{st.Address.Address}"); }
上述方法会报错,这是因为st.Address.Address并没有实际的数据,需要通过Include方法进行相关的导航属性加载,代码修改如下:
var dbContext = new EfDbContext(); var students= dbContext.Students.Include(x=>x.Address).Include(x=>x.Courses).ToList(); foreach (var st in students) { Console.WriteLine($"StudentId:{st.StudentId},Name:{st.Name},City:{st.Address.City}, Address:{st.Address.Address},Courses:{string.Join(",",st.Courses.Select(x=>x.Name))}"); }
Include可以连续包含多个属性,例如:
dbContext.Students.Include(x=>x.Address).Include(x=>x.Courses).ToList();
2.2 删除数据
using (var dbContext = new EfDbContext()) { var st = dbContext.Students.Single(x => x.StudentId == 2022001); dbContext.Remove(st); dbContext.SaveChanges(); }
将学号为2022001的学生删除后,发现相关的地址信息,以及选择的课程信息也被删除了,这是由于在设置的EFCore默认了删除的行为为级联的方式。