MVC3+EF4.1学习系列(四)----- ORM关系的处理

文章索引和简介

上篇文章 终于把基础的一些操作写完了 但是这些都是单表的处理 而EF做为一个ORM框架  就必须点说说对于关系的处理

 处理好关系 才能灵活的运用EF

关于关系的处理 一般就是  一对一   一对多  多对多  还有就是采用双向关联还是单项关联  而关系的处理  站长dudu的文章 就已经有了很好的介绍

推荐大家去看下 -------dudu的实体关系总结   这样大家对实体关系也就有了初步的认识了  但是在dudu的文章里 一直没有说如何处理多对多时 关系表

里有其他数据时怎么办(这个问题曾经困扰了我好久~~ 见人就问)  这里写下当时得到的几种方案 也希望能跟大家探讨下  好了 从实际项目开始 继续完善我们

的demo  并在从中探讨关系

先把原文中的完成后的图贴上来 也就是我们要处理的关系

这就是这次完成后样子    比以前多了几个类 关系也复杂了很多 下面我来解释这些关系

以及怎么建立的 一个一个慢慢来~~

一.创建教师实体

教师实体类
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace ContosoUniversity.Models
{
public class Instructor
{
public Int32 InstructorID { get; set; }

[Required(ErrorMessage
= "Last name is required.")]
[Display(Name
="Last Name")]
[MaxLength(
50)]
public string LastName { get; set; }

[Required(ErrorMessage
= "First name is required.")]
[Column(
"FirstName")]
[Display(Name
= "First Name")]
[MaxLength(
50)]
public string FirstMidName { get; set; }

[DisplayFormat(DataFormatString
= "{0:d}", ApplyFormatInEditMode = true)]
[Required(ErrorMessage
= "Hire date is required.")]
[Display(Name
= "Hire Date")]
public DateTime? HireDate { get; set; }

public string FullName
{
get
{
return LastName + ", " + FirstMidName;
}
}

public virtual ICollection<Course> Courses { get; set; }
public virtual OfficeAssignment OfficeAssignment { get; set; }
}
}

在第二篇文章里 简单的说了下用特性进行控制与MVC的结合 这里再详细的讲解几个

1.The Column Attribute

  [Column("FirstName")]
public string FirstMidName { get; set; }

利用这个特性 我们可以使FirstMidName属性映射到数据库的列为FirstName   在这里 如果使用[MaxLength(10)] 可以规定字段长度 [Required]这个来规定是否允许空 这是比较常用的 

2. 请大家注意这个FullName

 这个是不会被创建到数据库里的  因为它仅仅是获得 也就是一个get  这样是不会创建一个FullName列 在这个数据库表里的

3.普通的多对多的关系

 这篇文章是这样  一个老师可以教多个课程  一个课程 也可以被多个老师教   与我们平时的习惯似乎有些不符合 但是尊重原文 这里依然这样设计  于是这就是一个多对多的关系

于是有了这个导航属性

public virtual ICollection<Course> Courses { get; set; }

4.一对一的关系

每个老师都要有一个办公位置  而一个办公位置 也只应该有一个老师  所以这是一个一对一的关系 用dudu的话 采用两情相悦的方式 双向关联  ps:这个类还没建呢~~

public virtual OfficeAssignment OfficeAssignment { get; set; }

二.创建办公地点实体

办公地点实体类
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace ContosoUniversity.Models
{
public class OfficeAssignment
{
[Key]
public int InstructorID { get; set; }

[MaxLength(
50)]
[Display(Name
= "Office Location")]
public string Location { get; set; }

public virtual Instructor Instructor { get; set; }
}
}

一对一的关系和KEY特效

继续刚才的一对一  因为一个老师只有一个办公地点 一个办公地点只有一个老师  所以 这张表希望和教师表一样  都用InstructorID做为主键 (就像有的时候 1对1的关系 我们放在一张表里一样) 而通过前面的学习 我们知道  EF寻找主键 的规则是 命名为ID 或者命名为 类名+ID   而这里面 没有符合要求的   遇到这种情况  我们可以通过[key] 来指定让谁做主键

[Key]
public int InstructorID { get; set; }

三.修改课程实体

课程实体
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace ContosoUniversity.Models
{
public class Course
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Display(Name
= "Number")]
public int CourseID { get; set; }

[Required(ErrorMessage
= "Title is required.")]
[MaxLength(
50)]
public string Title { get; set; }

[Required(ErrorMessage
= "Number of credits is required.")]
[Range(
0,5,ErrorMessage="Number of credits must be between 0 and 5.")]
public int Credits { get; set; }

[Display(Name
= "Department")]
public int DepartmentID { get; set; }

public virtual Department Department { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
public virtual ICollection<Instructor> Instructors { get; set; }
}
}

1.The DatabaseGenerated Attribute

用这个特性是关闭自增长   然后用主键当课程编号  创建时需要输入 其实这样做我并不赞同 但是这篇文章主要是讲EF 所以不纠结于细节 可能这里主要是为了讲解这个特性吧

有兴趣的可以看下原文~~

2.一对多的关系

因为一个院系可以开多个课程  一个课程只能属于一个院系 所以这里是一对多关系  

在这个例子请注意 我们不仅拥有 院系的导航属性 还拥有一个导航属性ID 

public int DepartmentID { get; set; }
public virtual Department Department { get; set; }

当你的命名是这种情况时 聪明的EF 是不会在数据库里给你建两个列的  如果你依然担心  可以使用 ForeignKey特效  来制定外键的名字

但是这里我有个疑问  我看到很多人的设计  都喜欢这样子做  既有导航属性  又有导航属性ID  这样做的意义是什么呢?方便获取 设置初始化值么?还是什么?我用EF时 不推荐这么用

四.创建院系类

View Code
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace ContosoUniversity.Models
{
public class Department
{
public int DepartmentID { get; set; }

[Required(ErrorMessage
= "Department name is required.")]
[MaxLength(
50)]
public string Name { get; set; }

[DisplayFormat(DataFormatString
="{0:c}")]
[Required(ErrorMessage
= "Budget is required.")]
[Column(TypeName
="money")]
public decimal? Budget { get; set; }

[DisplayFormat(DataFormatString
="{0:d}", ApplyFormatInEditMode=true)]
[Required(ErrorMessage
= "Start date is required.")]
public DateTime StartDate { get; set; }

[Display(Name
="Administrator")]
public int? InstructorID { get; set; }

public virtual Instructor Administrator { get; set; }
public virtual ICollection<Course> Courses { get; set; }
}
}

一.The Column Attribute

[Column(TypeName="money")]
public decimal? Budget { get; set; }

通过这个特性 我们可以指定 生成到数据库里的列的属性

五.修改学生类

View Code
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace ContosoUniversity.Models
{
public class Student
{
public int StudentID { get; set; }

[Required(ErrorMessage
= "Last name is required.")]
[Display(Name
="Last Name")]
[MaxLength(
50)]
public string LastName { get; set; }

[Required(ErrorMessage
= "First name is required.")]
[Column(
"FirstName")]
[Display(Name
= "First Name")]
[MaxLength(
50)]
public string FirstMidName { get; set; }

[Required(ErrorMessage
= "Enrollment date is required.")]
[DisplayFormat(DataFormatString
= "{0:d}", ApplyFormatInEditMode = true)]
[Display(Name
= "Enrollment Date")]
public DateTime? EnrollmentDate { get; set; }

public string FullName
{
get
{
return LastName + ", " + FirstMidName;
}
}

public virtual ICollection<Enrollment> Enrollments { get; set; }
}
}

六.修改登记表(学生课程关系表)

修改登记表
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace ContosoUniversity.Models
{
public class Enrollment
{
public int EnrollmentID { get; set; }

public int CourseID { get; set; }

public int StudentID { get; set; }

[DisplayFormat(DataFormatString
="{0:#.#}",ApplyFormatInEditMode=true,NullDisplayText="No grade")]
public decimal? Grade { get; set; }

public virtual Course Course { get; set; }
public virtual Student Student { get; set; }
}
}

七.多对多关系的思考与讨论

项目的类和关系建完了 这里我最想和大家探讨与分享的就是 这个如何处理多对多的关系

看我们的例子  其实有两个多对多  一个是课程和教师   还有一个是学生和选课

课程与教师 我们是直接使用多对多 没有为关系表建立实体类  因为我们的关系表不需要存储其他数据

而在学生和选课这里 要存储成绩  关系表里要存储其他的东西  这时 我们为关系表也建立了实体类  关系变成里  学生 1对多  关系表   课程也是1对多关系表

也就是说 当遇到使用多对多时 关系表还需要存储其他东西时(本文是成绩)  可以把多对多拆穿 两个1对多  还有 实际项目不建议使用多对多来处理关系 

这是这个项目给的启事 请问大家是如何解决这种情况的呢? 所有的ORM都会遇到这种问题的吧?

这里 感谢下  桀骜的灵魂dax.net

他们在领域驱动群里 给了我另一种解答 直接上图

晴天 15:23:04
分数之间不需要区分个体,所以是值对象
晴天 15:23:23
学生和课程是需要系统维护的,所以是聚合根

晴天 15:25:13
CourseMark保存针对Course的引用
晴天 15:25:28
Student下包含多个CourseMark

dax.net  关于双向关联  

是不是双向关联不重要,首先要确定聚合根

班级是聚合根,学生也是聚合根  如果你的应用程序只是维护班级的基本信息,而不需要读取这个班级到底有哪些学生,那么就不需要往班级的聚合里添加学生

相反,通常情况下,学生的一个属性是班级,所以学生聚合里可以将班级加进去

八.关于EF的关系映射

看了这么多各种关系的类  他们之间存才的关系 多对多 一对一  一对多  处理这些映射是很麻烦的事 

我们有三种方法  1.按EF默认的规则 自动生成  但是有时生成的不是我们想要的 

                           2.通过特性的定义 来配置如何映射关系  以及如何映射到数据库  这篇文章提到了一些

                           3.通过流利的API 方式来定义   这也是我推荐的方式

看这篇文章的例子

using System;
using System.Collections.Generic;
using System.Data.Entity;
using ContosoUniversity.Models;
using System.Data.Entity.ModelConfiguration.Conventions;

namespace ContosoUniversity.Models
{
public class SchoolContext : DbContext
{
public DbSet<Course> Courses { get; set; }
public DbSet<Department> Departments { get; set; }
public DbSet<Enrollment> Enrollments { get; set; }
public DbSet<Instructor> Instructors { get; set; }
public DbSet<Student> Students { get; set; }
public DbSet<OfficeAssignment> OfficeAssignments { get; set; }

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove
<PluralizingTableNameConvention>();
modelBuilder.Entity
<Instructor>()
.HasOptional(p
=> p.OfficeAssignment).WithRequired(p => p.Instructor);
modelBuilder.Entity
<Course>()
.HasMany(c
=> c.Instructors).WithMany(i => i.Courses)
.Map(t
=> t.MapLeftKey("CourseID")
.MapRightKey(
"InstructorID")
.ToTable(
"CourseInstructor"));
modelBuilder.Entity
<Department>()
.HasOptional(x
=> x.Administrator);
}
}
}

依然很痛苦啊   这要多勇敢 才能把编写model 到再编写数据库映射等 写完  还有这么多api要记什么意思 实在是太痛苦了

不要怕 想用code fisrt  有干净的poco  但又不想写这些的  你们的福音来了  强烈推荐使用

Entity Framework Power Tools

可以根据数据库 一键生成想要的model 与映射    原子中也有介绍的了  介绍连接

生成后 和我们自己写的一样 而且更加规范 统一的映射管理 真的很完美~~

九.总结

通过这节 终于把ORM的关系理顺了 但是 这只是把关系建立起来   关系的处理远没结束

下一节   查找关联数据  如何利用好延迟加载与懒惰加载

posted on 2011-07-27 08:48  wlf  阅读(22523)  评论(49编辑  收藏  举报