EF CodeFirst系列(6)---配置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的主键和外键。实体类的代码如下:
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")]修饰即可。
在1对0/1关系中,student在没有studentaddress时可以保存成功,但是studentaddress没有分配student时进行保存就会抛出异常。
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不能保存 }
生成数据库如下:
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外键
2.生成不可空的外键(student必须有班级)
生成不可空的外键也很简单,只要让默认的外键不为空即可,代码如下:
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; } }
生成的数据库如下:
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(); } }
上一篇: c#基础之一
下一篇: 设计模式之简单工厂模式(C#语言描述)