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默认了删除的行为为级联的方式。

