EF CodeFirst系列(5)---配置1对1,1对多,多对多关系

这一节介绍EF CodeFirst模式中的1对0/1,1对多,多对多关系的配置,只有梳理清楚实体间的关系,才能进行愉快的开发,所以这节虽然很简单可是仍是记录了一下。数据库

1. 1对0/1关系配置

1. 经过数据注释属性配置1对0/1关系

咱们将要实现一个Student和StudentAddress实体的1对0/1关系,1对0/1关系指的是一个Student可有一个或者零个住址StudentAddress,可是一个StudentAddress必须对应一个Student。在数据库中表现形式是StudentId在Student表中是主键,StudentAddressId在数据库中同时是StudentAddress的主键和外键实体类的代码以下:ide

public class Student
{
    public int StudentId { get; set; }
    public string StudentName { get; set; }

    public virtual StudentAddress Address { get; set; }
}
     
public class StudentAddress 
{
    [ForeignKey("Student")]
    public int StudentAddressId { get; set; }
        
    public string Address1 { get; set; }
    public string Address2 { get; set; }
    public string City { get; set; }
    public int Zipcode { get; set; }
    public string State { get; set; }
    public string Country { get; set; }

    public virtual Student Student { get; set; }
}

上边代码中会遵循EF CodeFirst默认约定,StudentId做为Students表的主键,StudentAddressId做为StudentAddresses表的主键,咱们不须要本身去配置主键了,默认约定不会把StudentAddressId设置为指向Student实体的外键,这就咱们须要本身去配置。在StudentAddressId经过[ForeignKey("Student")]修饰便可ui

在1对0/1关系中,Student在没有StudentAddress时能够保存成功,可是StudentAddress没有分配Student时进行保存就会抛出异常。spa

2. 经过FluentApi配置1对0/1关系

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    // 配置Student和StudentAddress实体
    modelBuilder.Entity<Student>()
                .HasOptional(s => s.Address) // 给Student设置可空的StudentAddress属性
                .WithRequired(ad => ad.Student); //给StudentAddress设置不能为空的Student属性.没有Student时,StudentAddress不能保存
}

生成数据库以下:3d

2. 1对多关系配置

  这一部分介绍EF中CodeFirst模式下1对多关系的配置,咱们要实现Student和Grade的关系配置,一个学生只能有一个班级而一个班级能够有多个学生,实体类以下:指针

public class Student
{
    public int StudentId { get; set; }
    public string StudentName { get; set; }
}
       
public class Grade
{
    public int GradeId { get; set; }
    public string GradeName { get; set; }
    public string Section { get; set; }
}

1.经过默认约定配置1对多关系

 1.生成可空的外键(Student能够没有班级)

//Student实体中的Grade引用导航属性和Grade实体中的Students集合导航属性,二者有一个便可,生成的是可空的外键
public class Student
{
    public int Id { get; set; }
    public string Name { get; set; }
    public Grade Grade { get; set; }
}

public class Grade
{
    public int GradeID { get; set; }
    public string GradeName { get; set; }
    public string Section { get; set; }
    
    public ICollection<Student> Students { get; set; }
}

运行程序后生成的数据库以下,咱们看到生成了可空的Grade_GradeId外键code

2.生成不可空的外键(Student必须有班级)

 生成不可空的外键也很简单,只要让默认的外键不为空便可,代码以下:blog

public class Student
{
    public int Id { get; set; }
    public string Name { get; set; }
    
    public int GradeId { get; set; }//若是改为 public int? GradeId则生成可空的外键
    public Grade Grade { get; set; }
}

public class Grade
{

    public int GradeId { get; set; }
    public string GradeName { get; set; }
    
    public ICollection<Student> Student { get; set; }
}

生成的数据库以下:ip

2. 经过FluentApi配置1对多关系

  一般咱们不须要配置1对多的关系,由于EF的默认约定就能帮咱们很好地解决这个问题,若是为了让关系更好维护,咱们也能够经过FluentApi来配置1对多关系。开发

FluentApi配置1对多关系代码以下:

    public class SchoolContext : DbContext
    {
        public SchoolContext() : base()
        {
        }
        public DbSet<Student> Students { get; set; }
        public DbSet<Grade> Grade { get; set; }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Student>()
                .HasRequired(s => s.Grade)//Student有必需的导航属性Grade,这会建立一个not null的外键
                .WithMany(g => g.Students)//Grade实体有集合导航属性Student
                .HasForeignKey(s=> s.GradeId);//设置外键(若是Student中属性不遵循约定咱们本身指定外键,如HasForeignKey(s=>s.GradeKey))
        }
    }

咱们也能够经过Grade实体来实现Student和Grade的1对多关系,代码以下:

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Grade>()
                .HasMany(g => g.Students)
                .WithRequired(s => s.Grade)
                .HasForeignKey(s => s.GradeKey);
        }

运行程序,生成的数据库以下:

3. 配置1对多的级联删除

  级联删除指当删除父级记录时会自动删除子级记录,如删除班级时,将在这个班级的全部学生记录一并删除(EF中是将这个班级的Student中的GradeId列都设成null),经过FluentApi很容易配置级联删除。

modelBuilder.Entity<Grade>()
    .HasMany<Student>(g => g.Students)
    .WithRequired(s => s.CurrentGrade)
    .WillCascadeOnDelete();//开启级联删除,删除班级时会一并删除全部这个班级的学生
    //.WillCascadeOnDelete(false);不开启级联删除

一点补充:EF中的级联删除默认是打开的,EF中级联删除执行策略

  1对1:如Student和StudentAddress,删除Student时会把StudentAddress一并删除。

  1对多:如Student和Grade,删除Grade时,会把该年级下的学生的GradId设成为null。

  多对多:见下边的Student和Course,删除一门课程时,会删除中间表中该门课程的记录。

3.配置多对多关系

1.经过默认约定配置多对多关系

这一部分介绍多对多关系的配置,以Student和Course为例,一个学生能够学多门课,每门课的学生能够是多个。EF6包含了多对多关系的默认约定。在Student实体类添加一个Course的集合导航属性,在Course实体类下添加一个Student集合导航属性,不需额外的配置,EF会帮咱们建立Student和Course的多对多关系。代码以下:

    public class Student
    {
        public int StudentId { get; set; }
        public string StudentName { get; set; }
        public  ICollection<Course> Courses { get; set; }
    }

    public class Course
    {
        public int CourseId { get; set; }
        public string CourseName { get; set; }
        public ICollection<Student> Students { get; set; }
    }

    public class SchoolContext : DbContext
    {
        public SchoolContext() : base()
        {
        }
        public DbSet<Student> Students { get; set; }
        public DbSet<Course> Grade { get; set; }

    }

运行程序后生成的数据库以下,EF建立了Courses,Students表,同时建立了一个StudentCourses中间表:

2.经过FluentApi配置多对多关系

直接上代码:

    modelBuilder.Entity<Student>()
                .HasMany<Course>(s => s.Courses)//配置一个学生有多个课程
                .WithMany(c => c.Students)      //配置一门课程有多个学生
                .Map(cs =>
                        {
                            cs.MapLeftKey("StudentRefId");  //由于经过Entity<Student>()开始的,因此左表是Student
                            cs.MapRightKey("CourseRefId");  //右表是Course
                            cs.ToTable("StudentCourse");    //生成StudentCourse中间表
                        });

3.多对多的数据重置

在EF中若是中间表只有两个实体的主键列,那么EF会自动帮咱们维护,一个重置学生课程的案例(经常使用的User-Role-Action权限控制也能这样重置角色和权限):

       static void Main(string[] args)
        {
            using (SchoolContext context=new SchoolContext())
            {
                //必需要把对应的Courses查出来,否则添加时会报空指针异常
                var stu1 = context.Students.Include("Courses").Where(s=>s.StudentId==3).First();
                
                var co1 = context.Courses.Find(1);
                var co2= context.Courses.Find(2);
                //先清空再添加
                stu1.Courses.Clear();
                stu1.Courses.Add(co1);
                stu1.Courses.Add(co2);
                context.SaveChanges();
            }
        }