近期学习MVC5+EF6,找到了Microsoft的原文,一个非常棒的系列,Getting Started with Entity Framework 6 Code First using MVC 5,网址:http://www.asp.net/mvc/overview/getting-started/getting-started-with-ef-using-mvc/creating-an-entity-framework-data-model-for-an-asp-net-mvc-application。
这个系列的原文,可以上面网址找到。我也从网上找到了相关的译文。申明,这些译文,我不是原创,而是从网上找来的,为避免下次还要网上查询,现将这些译文整理放在上面。以后找时间将原文地址附上。
MVC5+EF6--6 创建更复杂的数据模型
原文网址:
Contoso University示例网站演示如何使用Entity Framework 5创建ASP.NET MVC 4应用程序。Entity Framework有三种处理数据的方式: Database First, Model First, and Code First. 本指南使用代码优先。其它方式请查询资料。示例程序是为Contoso University建立一个网站。功能包括:学生管理、课程创建、教师分配。 本系列指南逐步讲述如何实现这一网站程序。
本示例程序基于 ASP.NET MVC.如果使用 ASP.NET Web Forms model, 请查看 Model Binding and Web Forms系列指南和 ASP.NET Data Access Content Map.
如有问题,可在这些讨论区提问: ASP.NET Entity Framework forum, the Entity Framework and LINQ to Entities forum, or StackOverflow.com.
目前在指南中使用的数据模型由三个实体组成。这一节你将添加更多实体和它们之间的关系,也将通过格式指明、验证、数据库映射规则对数据模型进行定制。你将学习两种定制数据模型的方式:在实体类添加特性或在数据上下文类添加代码。
完成之后的实体及其关系如下:
使用特性定制数据模型
本小节你将学习如何使用格式指明、验证、数据映射规则等特性定制数据模型。随后将完成所需要的数据模型。
DataType 特性
尽管只关心学生登记的日期,但现在所有显示登记日期的网页也显示了时间。使用数据标注特性可修改一次代码即完成对所有显示格式的修复。
在 Models\Student.cs, 添加 using
语句引入 System.ComponentModel.DataAnnotations
命名空间,为EnrollmentDate
属性添加 DataType
和DisplayFormat
特性,代码如下:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace ContosoUniversity.Models
{
public class Student
{
public int StudentID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime EnrollmentDate { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
}
DataType 特性更明确的指明了数据的类型. 在此我们只关心日期,时间可以忽略. DataType Enumeration提供了很多数据类型, 比如 Date, Time, PhoneNumber, Currency, EmailAddress等等.DataType
特性还能让程序为数据类型提供相应的功能. 如,会为 DataType.EmailAddress添加mailto链接, 为 DataType.Date 在浏览器提供支持HTML5的日期选择.DataType 特性为 HTML 5 浏览器提供可识别的数据. DataType 不负责验证.
DataType.Date
并没有指明数据格式. 默认按服务器的 CultureInfo的格式.
DisplayFormat
用来明确指明数据格式:
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime EnrollmentDate { get; set; }
ApplyFormatInEditMode
指明是否在编辑状态使用此格式. (有时可能不需要 — 例如, 对于货币值, 编辑时你可能不希望在输入框出现货币符号.)
可单独使用 DisplayFormat ,但同时最好使用 DataType .DataType
特性将数据的语义转为如何显示,提供DisplayFormat
不具备的功能
:
- 浏览器启用 HTML5 功能(如显示日期控件, 适当的货币符号, email 链接等.).
- 浏览器默认使用 locale定义的格式显示数据.
- DataType 特性使得MVC选择正确的呈现方式 (DisplayFormat 需要指明格式字符串). 更多信息请查看 ASP.NET MVC 2 Templates.
如果使用了 DataType
,还要使用 DisplayFormat
特性的原因是确保能在Chrome浏览器显示正常. 更多信息请查看 this StackOverflow thread.
运行程序,查看使用 Student
模型的视图中关于日期是否不再显示时间.
StringLength特性
你也可以通过特性对数据验证规则和提示信息进行定制. 为了限制LastName
和FirstMidName
属性的长度不要超过50个字符,可使用StringLength特性:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace ContosoUniversity.Models
{
public class Student
{
public int StudentID { get; set; }
[StringLength(50)]
public string LastName { get; set; }
[StringLength(50, ErrorMessage = "First name cannot be longer than 50 characters.")]
public string FirstMidName { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime EnrollmentDate { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
}
StringLength 并不阻止输入空字符串。可使用RegularExpression 特性限制输入格式. 如下面的特性值限制输入的首字符必须是大写,随后输入的都是字母:
[RegularExpression(@"^[A-Z]+[a-zA-Z''-'\s]*$")]
MaxLength 特性和 StringLength 特性相似,但前者不提供客户端验证功能.
运行程序,点击Students . 将发生如下错误:
The model backing the 'SchoolContext' context has changed since the database was created. Consider using Code First Migrations to update the database (http://go.microsoft.com/fwlink/?LinkId=238269).
数据模型发生变化要求数据库格式相应变化, Entity Framework能检测到这一问题. 可使用迁移命令在数据不丢失的同时升级数据库.如果修改了通过 Seed
录入的数据, 将恢复到初始状态。这是因为Seed 方法中调用了 AddOrUpdate
在Package Manager Console (PMC), 输入如下命令:
add-migration MaxLengthOnNames
update-database
add-migration MaxLengthOnNames
创建了名为 <timeStamp>_MaxLengthOnNames.cs的文件. 其中包含了更新数据库以匹配当前数据模型的代码。文件名之前的时间戳被Entity Framework 用来识别迁移的顺序. 在创建了多次迁移之后,如果删除了数据库或者使用迁移部署项目,所有的迁移将按照创建顺序执行。
运行创建页面,如果姓名输入字符长度超出50,提示信息立即就显示了.
Column 属性
可使用特性控制类和属性如何映射到数据库。比如使用FirstMidName
作为名字,但数据库列名依然希望是FirstName,可使用Column
特性。Y
Column
特性指明数据库创建时,Student
表映射FirstMidName属性的列名是FirstName。换句话说,如果你的代码引用Student.FirstMidName,数据将来自Student
表的FirstName
列。如果不指明列名,列名和属性名将保持一致.
添加using引入 System.ComponentModel.DataAnnotations.Schema 命名空间,为FirstMidName
属性添加column 特性 ,代码如下:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class Student
{
public int StudentID { get; set; }
[StringLength(50)]
public string LastName { get; set; }
[StringLength(50, ErrorMessage = "First name cannot be longer than 50 characters.")]
[Column("FirstName")]
public string FirstMidName { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime EnrollmentDate { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
}
Column attribute 改变了 SchoolContext背后的模型, 和数据库不再匹配. 输入以下命令执行迁移:
add-migration ColumnFirstName
update-database
在 Server Explorer, 查看Student 表.
可使用 Fluent API完成数据库的映射, 随后将提到这一功能.
Note在完成所有模型的代码之前编译程序可能导致编译失败.
创建Instructor Entity
创建 Models\Instructor.cs, 代码如下:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class Instructor
{
public int InstructorID { get; set; }
[Required]
[Display(Name = "Last Name")]
[StringLength(50)]
public string LastName { get; set; }
[Required]
[Column("FirstName")]
[Display(Name = "First Name")]
[StringLength(50)]
public string FirstMidName { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
[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; }
}
}
在 Student
和Instructor
实体中有一些属性相同.你将在随后的 Implementing Inheritance 部分, 使用继承来消除冗余.
Required 和Display 特性
LastName
属性的Required特性指明此属性是不能为空的,Display属性指明了此属性显示名称.
[Required]
[Display(Name="Last Name")]
[StringLength(50)]
public string LastName { get; set; }
Required attribute 对 DateTime, int, double, 和float这样的值类型来说是不需要的. 值类型本身就不允许赋 null 值, 因此它们默认就是required. 为 StringLength
特性设定一个最短长度可以不必使用required特性了:
[Display(Name = "Last Name")]
[StringLength(50, MinimumLength=1)]
public string LastName { get; set; }
可在同一行写多个特性,因此 instructor 类也可以这样写:
public class Instructor
{
public int InstructorID { get; set; }
[Display(Name = "Last Name"),StringLength(50, MinimumLength=1)]
public string LastName { get; set; }
[Column("FirstName"),Display(Name = "First Name"),StringLength(50, MinimumLength=1)]
public string FirstMidName { get; set; }
[DataType(DataType.Date),Display(Name = "Hire Date"),DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
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; }
}
FullName 计算属性
FullName
是计算属性,其返回值来自其它两个属性的计算
. 因此它只有 get
访问器, 数据库中不会有 FullName
列.
public string FullName
{
get { return LastName + ", " + FirstMidName; }
}
Courses 和 OfficeAssignment 导航属性
之前曾经讲过导航属性,通常使用 virtual 以便利用 Entity Framework 的 lazy loading. 另外, 如果导航属性包含多个对象,它的类型必须是实现了 ICollection<T> 接口. (例如 IList<T> 可以,但 IEnumerable<T> 不行,因为IEnumerable<T>
没有实现 Add.
一个老师可以上多门课,因此 Courses
被定义为 Course
实体的集合. 我们的业务规则要求老师最多只能有一个办公室, 因此 OfficeAssignment
定义为OfficeAssignment
实体的单个变量(如果没有分配办公室,值可能是 null
).
public virtual ICollection<Course> Courses { get; set; }
public virtual OfficeAssignment OfficeAssignment { get; set; }
创建 OfficeAssignment Entity
创建 Models\OfficeAssignment.cs 代码如下:
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class OfficeAssignment
{
[Key]
[ForeignKey("Instructor")]
public int InstructorID { get; set; }
[StringLength(50)]
[Display(Name = "Office Location")]
public string Location { get; set; }
public virtual Instructor Instructor { get; set; }
}
}
编译,你需要保证编译通过.
Key 特性
在 Instructor
和OfficeAssignment
实体之间是1对0或1的关系. 因此office assignment的主键同时是 Instructor
的外键. 但 Entity Framework 不能自动识别 InstructorID
作为OfficeAssignment
的主键,因为其命名没有遵循默认主键是 ID
or classnameID
命名约定. 因此 Key特性用来指明主键:
[Key]
[ForeignKey("Instructor")]
public int InstructorID { get; set; }
如果主键的名字不想使用 classnameID
或ID
,你也可以使用
Key
特性指明主键
.
ForeignKey 特性
对于两个实体之间1对0或1的关系或者1对1的关系(如OfficeAssignment 和Instructor),EF无法识别哪一方是主方,哪一方是依赖方。1对1关系在每个类里都有导航属性指向对方。ForeignKey特性应用到依赖方的类,建立明确的关系,如果忽略了,你将在迁移时出现如下异常:
Unable to determine the principal end of an association between the types 'ContosoUniversity.Models.OfficeAssignment' and 'ContosoUniversity.Models.Instructor'. The principal end of this association must be explicitly configured using either the relationship fluent API or data annotations.
随后我们将讲解如何使用fluent API解决这一问题.
Instructor 导航属性
Instructor
实体有一个 nullable OfficeAssignment
导航属性 (老师可能么有分配办公室), OfficeAssignment
有一个 non-nullable Instructor
导航属性 (一个分配不可能没有老师-- InstructorID
是 non-nullable). 当一个Instructor
实体和 OfficeAssignment
实体有关系时, 二者相互有指向对方的导航属性.
你可以在Instructor 导航属性增加 [Required]
特性指明必须有一个 instructor, 但 InstructorID 外键 (同时也是此表的主键) 已经是 non-nullable,因此不必添加.
修改 Course Entity
在 Models\Course.cs, 修改代码为如下:
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class Course
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Display(Name = "Number")]
public int CourseID { get; set; }
[StringLength(50, MinimumLength = 3)]
public string Title { get; set; }
[Range(0, 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; }
}
}
course实体的DepartmentID
外键指向和它相关的Department实体,同时还有一个Department导航属性 。在EF中,当有导航属性后就不需要外键属性了。EF会在数据库自动创建需要的外键。但是在数据模型中使用外键使得更新更简单高效。例如,当你修改一个course实体,如果没有加载Department
,则其值为null,当更新course实体时,你需要首先找到Department
实体。如果数据模型包含了DepartmentID
外键,就不需要这么做了。
DatabaseGenerated 特性
DatabaseGenerated特性的 None 指明 CourseID
属性的值虽然是主键,但由用户赋值而非数据库自动生成.
[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Display(Name = "Number")]
public int CourseID { get; set; }
Entity Framework 默认主键值由数据库自动生成.有时不希望这样时,就可以使用 DatabaseGenerated
.
外键和导航属性
Course
的外键和导航属性反映了如下关系:
- course隶属一个 department, 因此有
DepartmentID
外键和Department
导航属性
. - A course 有多个 student注册登记,因此
Enrollments
导航属性是一个集合: - A course 可能由 instructors讲授, 因此
Instructors
导航属性是集合:
· 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; }
创建Department Entity
创建 Models\Department.cs 代码如下:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class Department
{
public int DepartmentID { get; set; }
[StringLength(50, MinimumLength=3)]
public string Name { get; set; }
[DataType(DataType.Currency)]
[Column(TypeName = "money")]
public decimal Budget { get; set; }
[DataType(DataType.Date)]
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; }
}
}
Column 特性
之前使用 Column attribu修改映射的列名.在 Department
实体,Column
特性用来修改 SQL数据类型以便数据库中此列使用 money类型:
[Column(TypeName="money")]
public decimal Budget { get; set; }
通常Entity Framework会为数据库的列选择适当的类型。这里只是进一步明确了数据库要使用的类型。
外键和导航属性
外键和导航属性如下:
- 一个部门可能有一个管理员,管理员也是老师,因此这里有如下外键和属性:
- A department 可以有多门 courses, 因此有一个
Courses
导航属性
· public int? InstructorID { get; set; }
public virtual Instructor Administrator { get; set; }
public virtual ICollection<Course> Courses { get; set; }
注意:EF在多对多关系中删除不为空的外键时采用瀑布模式。这可能导致循环瀑布删除,从而引发异常:“引用关系导致循环引用”。如果业务规则要求InstructorID
不为空,可使用如下代码禁用瀑布删除:
modelBuilder.Entity().HasRequired(d => d.Administrator).WithMany().WillCascadeOnDelete(false);
修改 Student Entity
在 Models\Student.cs, 替换代码如下.
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class Student
{
public int StudentID { get; set; }
[StringLength(50, MinimumLength = 1)]
public string LastName { get; set; }
[StringLength(50, MinimumLength = 1, ErrorMessage = "First name cannot be longer than 50 characters.")]
[Column("FirstName")]
public string FirstMidName { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", 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; }
}
}
Enrollment Entity
Models\Enrollment.cs, 代码如下
using System.ComponentModel.DataAnnotations;
namespace ContosoUniversity.Models
{
public enum Grade
{
A, B, C, D, F
}
public class Enrollment
{
public int EnrollmentID { get; set; }
public int CourseID { get; set; }
public int StudentID { get; set; }
[DisplayFormat(NullDisplayText = "No grade")]
public Grade? Grade { get; set; }
public virtual Course Course { get; set; }
public virtual Student Student { get; set; }
}
}
外键和导航属性
关系如下:
- 一条登记信息对应一门课程,因此有
CourseID
属性和Course
导航属性
: - 一条登记信息对应一个学生,因此有
StudentID
外键属性Student
导航属性:
· public int CourseID { get; set; }
public virtual Course Course { get; set; }
· public int StudentID { get; set; }
public virtual Student Student { get; set; }
多对多关系
在 Student
和Course
实体之间是多对多关系, Enrollment
实体的作用是作为二者之间的连接表
. 也就是说 Enrollment
表包含额外的外键之外的信息(本例中,主键和Grade属性).
下图显示了这些实体之间的关系. (图是使用 Entity Framework Power Tools生成的; )
由两个一对多的关系组成.
如果 Enrollment
不需要保存Grade信息,只需要CourseID
and StudentID
. 就成了单纯的连接表不包含额外信息, 那Enrollment
实体就无需存在了. Instructor
和Course
直接建立多对多的关系:
但数据库中还是需要连接表的:
Entity Framework自动创建了 CourseInstructor
表, 在读取和更新时你会间接用到它。
实体图显示了实体之间的关系
Entity Framework Power Tools创建的完整的 School 模型图.
实体间有一对一关系、一对一或零关系,一对多关系和多对多关系.
向 Database Context添加代码实现模型定制
下一步向 SchoolContext
添加新的实体并使用fluent API定制映射关系.
在本指南中你将使用fluent API完成无法通过特性实现的数据映射。但你也可使用它指明格式、验证等通过特性完成的工作,有些是无法完成的,如MinimumLength
,如前面说的MinimumLength
不改变数据库的结构,只是客户端或服务端的验证。一些开发人员优先使用fluentAPI以保持他们的代码“干净”。
为了添加新实体到数据模型同时定制通过特性无法实现的数据库映射T使用如下代码替换 DAL\SchoolContext.cs:
using ContosoUniversity.Models;
using System.Data.Entity;
using System.Data.Entity.ModelConfiguration.Conventions;
namespace ContosoUniversity.DAL
{
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<Course>()
.HasMany(c => c.Instructors).WithMany(i => i.Courses)
.Map(t => t.MapLeftKey("CourseID")
.MapRightKey("InstructorID")
.ToTable("CourseInstructor"));
}
}
}
OnModelCreating 方法中的新代码建立了多对多的连接表:
· 在 Instructor 和 Course 实体之间,代码指明链接表和列的名字.代码优先可使得无需代码即可完成多对多的关系,但如果不写代码可能会使用 InstructorInstructorID 作为InstructorID 列的名字.
· modelBuilder.Entity<Course>()
· .HasMany(c => c.Instructors).WithMany(i => i.Courses)
· .Map(t => t.MapLeftKey("CourseID")
· .MapRightKey("InstructorID")
· .ToTable("CourseInstructor"));
下面的代码是使用fluent API完成 Instructor 和 OfficeAssignment 实体之间的关系::
modelBuilder.Entity<Instructor>()
.HasOptional(p => p.OfficeAssignment).WithRequired(p => p.Instructor);
更多信息请查看 Fluent API .
使用测试数据向数据库添加数据
替换 Migrations\Configuration.cs 代码,添加新实体的测试数据
namespace ContosoUniversity.Migrations
{
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.Entity.Migrations;
using System.Linq;
using ContosoUniversity.Models;
using ContosoUniversity.DAL;
internal sealed class Configuration : DbMigrationsConfiguration<SchoolContext>
{
public Configuration()
{
AutomaticMigrationsEnabled = false;
}
protected override void Seed(SchoolContext context)
{
var students = new List<Student>
{
new Student { FirstMidName = "Carson", LastName = "Alexander",
EnrollmentDate = DateTime.Parse("2010-09-01") },
new Student { FirstMidName = "Meredith", LastName = "Alonso",
EnrollmentDate = DateTime.Parse("2012-09-01") },
new Student { FirstMidName = "Arturo", LastName = "Anand",
EnrollmentDate = DateTime.Parse("2013-09-01") },
new Student { FirstMidName = "Gytis", LastName = "Barzdukas",
EnrollmentDate = DateTime.Parse("2012-09-01") },
new Student { FirstMidName = "Yan", LastName = "Li",
EnrollmentDate = DateTime.Parse("2012-09-01") },
new Student { FirstMidName = "Peggy", LastName = "Justice",
EnrollmentDate = DateTime.Parse("2011-09-01") },
new Student { FirstMidName = "Laura", LastName = "Norman",
EnrollmentDate = DateTime.Parse("2013-09-01") },
new Student { FirstMidName = "Nino", LastName = "Olivetto",
EnrollmentDate = DateTime.Parse("2005-09-01") }
};
students.ForEach(s => context.Students.AddOrUpdate(p => p.LastName, s));
context.SaveChanges();
var instructors = new List<Instructor>
{
new Instructor { FirstMidName = "Kim", LastName = "Abercrombie",
HireDate = DateTime.Parse("1995-03-11") },
new Instructor { FirstMidName = "Fadi", LastName = "Fakhouri",
HireDate = DateTime.Parse("2002-07-06") },
new Instructor { FirstMidName = "Roger", LastName = "Harui",
HireDate = DateTime.Parse("1998-07-01") },
new Instructor { FirstMidName = "Candace", LastName = "Kapoor",
HireDate = DateTime.Parse("2001-01-15") },
new Instructor { FirstMidName = "Roger", LastName = "Zheng",
HireDate = DateTime.Parse("2004-02-12") }
};
instructors.ForEach(s => context.Instructors.AddOrUpdate(p => p.LastName, s));
context.SaveChanges();
var departments = new List<Department>
{
new Department { Name = "English", Budget = 350000,
StartDate = DateTime.Parse("2007-09-01"),
InstructorID = instructors.Single( i => i.LastName == "Abercrombie").InstructorID },
new Department { Name = "Mathematics", Budget = 100000,
StartDate = DateTime.Parse("2007-09-01"),
InstructorID = instructors.Single( i => i.LastName == "Fakhouri").InstructorID },
new Department { Name = "Engineering", Budget = 350000,
StartDate = DateTime.Parse("2007-09-01"),
InstructorID = instructors.Single( i => i.LastName == "Harui").InstructorID },
new Department { Name = "Economics", Budget = 100000,
StartDate = DateTime.Parse("2007-09-01"),
InstructorID = instructors.Single( i => i.LastName == "Kapoor").InstructorID }
};
departments.ForEach(s => context.Departments.AddOrUpdate(p => p.Name, s));
context.SaveChanges();
var courses = new List<Course>
{
new Course {CourseID = 1050, Title = "Chemistry", Credits = 3,
DepartmentID = departments.Single( s => s.Name == "Engineering").DepartmentID,
Instructors = new List<Instructor>()
},
new Course {CourseID = 4022, Title = "Microeconomics", Credits = 3,
DepartmentID = departments.Single( s => s.Name == "Economics").DepartmentID,
Instructors = new List<Instructor>()
},
new Course {CourseID = 4041, Title = "Macroeconomics", Credits = 3,
DepartmentID = departments.Single( s => s.Name == "Economics").DepartmentID,
Instructors = new List<Instructor>()
},
new Course {CourseID = 1045, Title = "Calculus", Credits = 4,
DepartmentID = departments.Single( s => s.Name == "Mathematics").DepartmentID,
Instructors = new List<Instructor>()
},
new Course {CourseID = 3141, Title = "Trigonometry", Credits = 4,
DepartmentID = departments.Single( s => s.Name == "Mathematics").DepartmentID,
Instructors = new List<Instructor>()
},
new Course {CourseID = 2021, Title = "Composition", Credits = 3,
DepartmentID = departments.Single( s => s.Name == "English").DepartmentID,
Instructors = new List<Instructor>()
},
new Course {CourseID = 2042, Title = "Literature", Credits = 4,
DepartmentID = departments.Single( s => s.Name == "English").DepartmentID,
Instructors = new List<Instructor>()
},
};
courses.ForEach(s => context.Courses.AddOrUpdate(p => p.CourseID, s));
context.SaveChanges();
var officeAssignments = new List<OfficeAssignment>
{
new OfficeAssignment {
InstructorID = instructors.Single( i => i.LastName == "Fakhouri").InstructorID,
Location = "Smith 17" },
new OfficeAssignment {
InstructorID = instructors.Single( i => i.LastName == "Harui").InstructorID,
Location = "Gowan 27" },
new OfficeAssignment {
InstructorID = instructors.Single( i => i.LastName == "Kapoor").InstructorID,
Location = "Thompson 304" },
};
officeAssignments.ForEach(s => context.OfficeAssignments.AddOrUpdate(p => p.Location, s));
context.SaveChanges();
AddOrUpdateInstructor(context, "Chemistry", "Kapoor");
AddOrUpdateInstructor(context, "Chemistry", "Harui");
AddOrUpdateInstructor(context, "Microeconomics", "Zheng");
AddOrUpdateInstructor(context, "Macroeconomics", "Zheng");
AddOrUpdateInstructor(context, "Calculus", "Fakhouri");
AddOrUpdateInstructor(context, "Trigonometry", "Harui");
AddOrUpdateInstructor(context, "Composition", "Abercrombie");
AddOrUpdateInstructor(context, "Literature", "Abercrombie");
context.SaveChanges();
var enrollments = new List<Enrollment>
{
new Enrollment {
StudentID = students.Single(s => s.LastName == "Alexander").StudentID,
CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID,
Grade = Grade.A
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Alexander").StudentID,
CourseID = courses.Single(c => c.Title == "Microeconomics" ).CourseID,
Grade = Grade.C
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Alexander").StudentID,
CourseID = courses.Single(c => c.Title == "Macroeconomics" ).CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Alonso").StudentID,
CourseID = courses.Single(c => c.Title == "Calculus" ).CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Alonso").StudentID,
CourseID = courses.Single(c => c.Title == "Trigonometry" ).CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Alonso").StudentID,
CourseID = courses.Single(c => c.Title == "Composition" ).CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Anand").StudentID,
CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Anand").StudentID,
CourseID = courses.Single(c => c.Title == "Microeconomics").CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Barzdukas").StudentID,
CourseID = courses.Single(c => c.Title == "Chemistry").CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Li").StudentID,
CourseID = courses.Single(c => c.Title == "Composition").CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Justice").StudentID,
CourseID = courses.Single(c => c.Title == "Literature").CourseID,
Grade = Grade.B
}
};
foreach (Enrollment e in enrollments)
{
var enrollmentInDataBase = context.Enrollments.Where(
s =>
s.Student.StudentID == e.StudentID &&
s.Course.CourseID == e.CourseID).SingleOrDefault();
if (enrollmentInDataBase == null)
{
context.Enrollments.Add(e);
}
}
context.SaveChanges();
}
void AddOrUpdateInstructor(SchoolContext context, string courseTitle, string instructorName)
{
var crs = context.Courses.SingleOrDefault(c => c.Title == courseTitle);
var inst = crs.Instructors.SingleOrDefault(i => i.LastName == instructorName);
if (inst == null)
crs.Instructors.Add(context.Instructors.Single(i => i.LastName == instructorName));
}
}
}
和之前一样,只是添加了测试用的数据. 不过要注意 Course
实体, 它和 Instructor
实体存在多对多关系,处理的方法是:
var courses = new List<Course>
{
new Course {CourseID = 1050, Title = "Chemistry", Credits = 3,
Department = departments.Single( s => s.Name == "Engineering"),
Instructors = new List<Instructor>()
},
...
};
courses.ForEach(s => context.Courses.AddOrUpdate(p => p.CourseID, s));
context.SaveChanges();
当创建Course
对象,你需要使用代码Instructors = new List<Instructor>()初始化Instructiors导航属性为一个空集合,这样可以使用Instructors.Add方法添加和 Course
相关的
Instructor
。
如果不初始化空列表则无法使用
Add
方法向其中添加,你也可以在构造函数中初始化列表。
添加迁移,更新数据库
使用如下命令添加迁移:
PM> add-Migration Chap4
此时如果更新数据库,将发生如下错误:
The ALTER TABLE statement conflicted with the FOREIGN KEY constraint "FK_dbo.Course_dbo.Department_DepartmentID". The conflict occurred in database "ContosoUniversity", table "dbo.Department", column 'DepartmentID'.
编辑<timestamp>_Chap4.cs 文件, 修改代码如下 (添加一条SQL语句,修改 AddColumn
语句):
CreateTable(
"dbo.CourseInstructor",
c => new
{
CourseID = c.Int(nullable: false),
InstructorID = c.Int(nullable: false),
})
.PrimaryKey(t => new { t.CourseID, t.InstructorID })
.ForeignKey("dbo.Course", t => t.CourseID, cascadeDelete: true)
.ForeignKey("dbo.Instructor", t => t.InstructorID, cascadeDelete: true)
.Index(t => t.CourseID)
.Index(t => t.InstructorID);
// Create a department for course to point to.
Sql("INSERT INTO dbo.Department (Name, Budget, StartDate) VALUES ('Temp', 0.00, GETDATE())");
// default value for FK points to department created above.
AddColumn("dbo.Course", "DepartmentID", c => c.Int(nullable: false, defaultValue: 1));
//AddColumn("dbo.Course", "DepartmentID", c => c.Int(nullable: false));
AlterColumn("dbo.Course", "Title", c => c.String(maxLength: 50));
AddForeignKey("dbo.Course", "DepartmentID", "dbo.Department", "DepartmentID", cascadeDelete: true);
CreateIndex("dbo.Course", "DepartmentID");
}
public override void Down()
{
有时在执行已存在数据的迁移时,需要添加根数据以满足外键约束,如上所做的一样。生成的代码为Course
表添加了一个非空外键DepartmentID。如果Course
表已经有数据,AddColumn
操作将失败,因为SQL Server不知道该为不能为空的列赋什么值。改变代码给它一个默认值,创建名为Temp的一个根department来作为默认department。这样的话,如果已经存在数据,则和“Temp”相关。
Seed
方法运行时,将把“Temp”数据插入到Department
表,将已经存在的Course
表中的记录和它相关联。
修改 <timestamp>_Chap4.cs 文件后, 运行 update-database
命令执行迁移.注意:
在迁移数据或修改结构时可能发生错误。如果你解决不了错误,可以修改配置文件中的连接字符串或者删除数据库以便重新生成。最简单的办法就是修改连接字符串中的数据库名字,比如:
<add name="SchoolContext" connectionString="Data Source=(LocalDb)\v11.0;Initial Catalog=CU_Test;
Integrated Security=SSPI;AttachDBFilename=|DataDirectory|\CU_Test.mdf"
providerName="System.Data.SqlClient" />
如何删除数据库请查看 How to Drop a Database from Visual Studio 2012.
这是最新的数据库结构:
你并没有为 CourseInstructor
表创建模型类.如之前所提到的,它只是 Instructor
和Course
实体之间的连接表.
右击CourseInstructor
表选择查看数据,查看表中是否有你加入 Course.Instructors
导航属性的
Instructor
相关信息
.
总结
你已经创建了更加复杂的数据模型和相应的数据库。随后你将学习更多访问关联数据的方法。
Entity Framework 相关资源请查看 ASP.NET Data Access Content Ma