【EntityFramework系列教程四,翻译】为ASP.NET MVC程序创建更为复杂的数据模型

在前几篇中你已经处理了由3个实体组成的数据模型,本章节中您将添加更多的数据实体以及关系,并且充分利用数据标注属性进一步地控制模型类的行为。

以下便是当你完成这次任务之后完整的数据类模型:

School_class_diagram

【使用属性控制格式化数据、数据验证及数据库映射】

在这部分您将可以看到用于数据模型类的属性的若干例子,用以控制数据的格式化,验证以及数据库的映射。接着在稍后几章中您将创建一个完整的School数据模型,您通过对已经创建的类添加属性,并且通过创建新类用于维护模型中的实体类型。

1)DisplayFormat属性:

对于学生选课日期可能你在乎的仅是“日期”,但是显示的结果却都带有时间;使用数据标记属性你只需做一步代码的改变就完全可以修正此问题。预见此示例,请先在Student实体类中添加“入学时间”(EnrollmentDate),如下所示:

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; }

[DisplayFormat(DataFormatString = "{0:d}", ApplyFormatInEditMode = true)]
public DateTime EnrollmentDate { get; set; }

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

此格式化字符串指定仅为此显示一个短日期(不带时间),ApplyFormatInEditmMode属性表示此属性同样被应用于编辑状态时的对文本框中内容也做此格式化处理(并不是所有文本框中的内容都需要如此处理——譬如货币类型,在编辑状态下你并不希望看到货币符号)。

再次运行Index页,注意“入学日期”再也不带时间了;如果你运行其它Student页面结果也是如此。

Students_index_page_with_formatted_date

2)MaxLength属性:

你同样也可以指定数据验证规则和出错提示性信息。假设你希望用户输入的内容不超过50个字符,为做这个显示,你必须为“LastName”和“FirstName”加上“Range”属性,如下所示:

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

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

[MaxLength(50)]
public string LastName { get; set; }

[MaxLength(50, ErrorMessage = "First name cannot be longer than 50 characters.")]
public string FirstMidName { get; set; }

[DisplayFormat(DataFormatString = "{0:d}", ApplyFormatInEditMode = true)]
public DateTime EnrollmentDate { get; set; }

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

如果一个用户企图输入一个很长的姓,默认错误信息就显示出来。此同样适用于很长的“名”。
运行“Create”页,输入两个超过50个字符的名称,点击Create查看出错提示信息(此证明你必须输入合理长度的名称才可以通过此验证)。

Students_Create_page_with_name_length_errors

指定字符串最大长度属性总是一个好主意——如果你不指定,当“Code-First”创建数据库,默认生成对应的字段的长度将是最长长度,这将是无效率表结构。

3)Column属性:

你可以使用这个属性指定你的类和属性如何与实际数据表相对应。假设你有一个FirstMidName作为“名”,可能这个“名”还包括中间名,但是你希望数据表的字段只是“FirstName”,因为人们查询时通常习惯使用“名”,为了做此映射,你应该用Column属性。

当数据库被创建后,Column属性就指定FirstMidName属性被命名为FirstName;换句话说,当你使用Student.FirstMidName的时候实际上是从FirstName中取出或是更新内容(若你不指定Column,那么实际字段和默认字段保持一致)。

对FirstMidName添加Column名,如下所示:

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

再次运行Index中Student页,你看不到有什么发生了改变(你不能仅运行页面然后查看Index页,你应该选择Index页的Student部分,因为这会导致数据库重新被访问,这样使得数据库被重新删除重建);然而,如果你在Server Explorer中打开数据库,那么当你展开“Student”之后就发现字段变成了FirstName。
Server_Explorer_showing_FirstName_column

在“属性”窗口中你注意到与之相关一些字段被定义成了50个字符长度,非常感谢MaxLength这个属性。

Properties_Window_showing_FirstName_column_length

在大部分情况下,你也可以使用代码的方式实现映射改变;您将在陆续的后文中看到。

接下来的内容中——就像你扩展School数据模型一样,你将充分利用数据标记属性;在每一个部分中,你将为实体创建一个类,或者改变你在第一章中已经创建的实体类。

注意:请不要在完成所有类的创建之前编译程序,不然会发生编译错误。

【创建Instructor类】

Instructor_entity

创建“Models\Instructor.cs”,并用以下代码替换:

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; }
}
}

请注意这些属性在Student和Instructor类中都是一样的,在稍后“继承实现”一章节中我们将通过继承重构这些类以便消除冗余。
1)Required和Display属性:

在LastName上的属性指定了该属性是必填项,文本框的名称是“Last Name”而不是“LastName”(不带有空格)。并且那个数值不能超过50个字符。

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

2)完整的字段组合属性:

FullName返回一个把其余两个字段值拼接起来的“组合”属性,因此它只有一个get属性,并且不在数据表
中生成该对应字段。

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

3)Courses和OfficeAssignment导航属性:
Courses和OfficeAssignment属性是两个导航属性——正如之前所描述的一样,它们被典型地定义为“virtual”,所以它们可以充分利用EntityFramework的“慢加载”优势。除此之外,如果导航属性可以包含多个实体,那么它必须是ICollection类型。

一个教员可以教授多门课程,所以Courses被定义为一系列Course的集合;另一方面,一个教员只能隶属于一个办公室,因此OfficeAssignment被定义成单个的实体(如果没有办公室,则为null)。

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

【创建OfficeAssign实体】

OfficeAssignment_entity

创建“Models\OfficeAssignment.cs”,并用以下代码替换:

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; }
}
}

1)Key属性:
在“Instructor”和“OfficeAssignment”之间存在“一对一”和“一对零”关系,一个办公室分配仅出现在和其相关的那个办公室,因此对于Instructor而言,主键也是其“外键”;但是EntityFramework并不知道这个实体的主键,因为其命名规则没有依照“类名ID”或者“ID”的规则。因此,Key属性在此时就要派上用场了,强制指定此是本表的主键。

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

如果一个实体有主键,你也可以使用Key属性重命名,而不是ID或者是“类名ID”的形式。

2)Instructor导航属性:

Instructor实体有一个可空的属性OfficeAssignment(因为一个Instructor可能没有一个办公室分配),而OfficeAssignment实体则拥有一个不为空的Instructor属性,因为一个办公室不可能在无老师情况下分配;当一个Instructor有一个相关的OfficeAssignment实体时,每一个实体在其导航属性中都有一个Instructor的对应分配。

【对Course实体修改】

Course_entity

在“”中,用以下代码替换你早期的代码:

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)DataBaseGenerated属性:

不带任何参数的DataBaseGenerated作用于CourseId上表示主键内容由用户而非数据库自动生成:

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

默认情况下,EntityFramework假定主键是通过数据库自生成的,那是你大部分情况下所需要的情形;然而对于Course实体,你将使用用户指定的数值作为课程号(比如1000,2000……等)。

2)外键以及导航属性:

Course中的外键属性和导航属性反映在以下关系中:

2.1)一个课程分配给了一个系,那么应该有一个Department和一个DepartmentId外键。

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

2.2)一个课程可以有很多学生选,自然有Enrollments导航属性:

public virtual ICollection Enrollments { get; set; }

2.3)一个课程可以有多个教师任教,因此有一个Instructor导航属性:

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

【创建Department实体】
Department_entity

创建“Models\Department.cs”,并且用以下代码进行替换:

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; }
}
}

1)Column属性:

先前说过可以使用Column改变列字段的名称,此处你使用Column来改变Department实体;Column属性也可以被使用来改变列的类型——如下例子中使用SQL Server的money类型:

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

通常而言你不必这样做,因为EntityFramework基于CLR(通用语言运行时)来寻找对应你属性类型的那个合适的SQL数据类型。CLR的decimal类型也将被数据库中映射成decimal类型。不过此处你知道该属性将存储货币类型的数据,自然money更为适合。

2)外键和导航属性:

外键和导航属性映射以下关系:

一个系可能有一个管理员,也可能没有,并且该管理员总是教员,自然InstructorID属性作为对Instructor实体对应的外键存在;问号标识符表示该int属性可以为空,导航属性被命名为Administrator,但是确实Instructor类型。

public int? InstructorID { get; set; }
public virtual Instructor Administrator { get; set; }

一个系有多个课程,所以有Courses导航属性:

public virtual ICollection Courses { get; set; }

注意:通过此转换,默认情况下EntityFramework对非空外键和“多对多”的关系表自生成“级联删除”,这可能导致循环级联删除,那么当你初始化代码运行时候将会引发异常;如果你不把Department.InstructorID定义为可空类型,那么你会得到“The referential relationship will result in a cyclical reference that's not allowed.”(引用关系将导致一个循环引用,此是不允许的)错误。

【变更Student实体类】

Student_entity

在“Models\Student.cs”中,请用以下代码替换现有代码:

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; }
}
}

以上代码,正如你在其它类中看到的一样,添加了一些属性。

【变更Enrollment实体】

Enrollment_entity

在“Models\Enrollment.cs”中,用以下代码替换现有代码:

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)导航和外键属性:

外键和导航属性表现为以下形式:

一个enrollment记录是单个课程,所以总是存在着一个CourseID外键和Course导航属性:

public int CourseID { get; set; }
public virtual Course Course { get; set; }

一个enrollment记录同样也代表了一个学生,因此有StudentID作为其外键和Student导航属性:

public int StudentID { get; set; }
public virtual Student Student { get; set; }

2)“多对多”关系:

在“Student”以及“Course”实体中存在一个“多对多”的关系,Enrollment表就担负着这个“多对多”的重担。这意味着Enrollment表包含了来自其它表的附加字段数据用以把表连接起来(本例子中主键和Grade属性就是)。

以下例子通过实体关系图中展示了这些关系(这个实体图是通过EntityFramework生成的,创建实体关系图并不是本系列所涉及的部分,只是为了方便说明而已):

Student-Course_many-to-many_relationship

每个关系在一端有“1”,另一端则是星;表明了一对多关系。

如果Enrollment表不包含grade消息,那么它只需要包含两个外键——CourseID和StudentID;在那种情况下就直接表现出“多对多”关系,而不是通过上面的“负载表”(纯用于连结两边关系的临时表),因此您根本不必为此创建一个模型类;Instructor和Course实体有这种“多对多”关系的表,因此自然它们之间不存在实体类连接:

Instructor-Course_many-to-many_relationship

以上的数据模型关系在实际的数据表中却是需要一个连接表的:

Instructor-Course_many-to-many_relationship_tables

EntityFramework自动生成CourseInstructor表,不过你直接可以通过Instructor.Courses或者Course.Instructors导航属性读取或者设置。

3)DisplayFormat属性:

Grade属性上的DisplayFormat表现了数据将如何被格式化并且呈现:

[DisplayFormat(DataFormatString="{0:#.#}",ApplyFormatInEditMode=true,NullDisplayText="No grade")]
public decimal? Grade { get; set; }
  • 分数由两位数组成,当中有实心句号分割。
  • 该格式化同样作用于文本框中。
  • 如果没有分数(在decimal后面加上问号),那么“No grade”就显示出来。

【展现关系的实体关系图】

以下关系使用(数据库优先)的方式展示了这个学校模型关系图:

School_data_model_diagram

除了“多对多”和“多对一”之外,在“Instructor”和“OfficeAssignment”实体中你还可以看到“一对零”或者“一对一”关系;同样在“Instructor”和“Department”中你还可以看到“零对一”或“零对多”关系。

【自定义数据库内容】

下一步你将为SchoolContext类添加新实体,并且用“流式”API自定义进行映射(API之所以是“流式”是因为在一个简单的表达式中用一连串的方法组合调用)。在某些情形下你应该用方法,而不是属性;那是因为对于特定的某些方法,没有对应的属性。其余情况下当“属性”和“方法”都可以使用时,你可以选择任意一种使用(某些人不喜欢使用属性)。

用下列代码替换“DAL\SchoolContext.cs”中的代码:

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);
}
}
}

在OnModelCreating方法中新表达式指定了以下关系:

在“Instructor”和“OfficeAssignment”之间“一对零”或“一对一”关系:

modelBuilder.Entity<Instructor>()
.HasOptional(p => p.OfficeAssignment).WithRequired(p => p.Instructor);

在“Instructor”和“Course”之间存在多对多关系。代码为连接表指定了表和列名;“代码优先”可以在不使用该代码的情况下配置“多对多”关系;但是如果你不调用它,你会得到自生成的名称(譬如:InstructorID变成了InstructorInstructorID)。

modelBuilder.Entity<Course>()
.HasMany(c => c.Instructors).WithMany(i => i.Courses)
.Map(t => t.MapLeftKey("CourseID")
.MapRightKey("InstructorID")
.ToTable("CourseInstructor"));

在Instructor和Department中“零对多”或“一对多”关系(换句话说,一个Department可以有也可以没有指定教员作为管理员),指定分配的管理员通过Department.Administrator导航属性表现出来:

modelBuilder.Entity<Department>()
.HasOptional(x => x.Administrator);

欲了解更多有关“流式API”消息,请参阅 Fluent API (由ASP.NET 用户使用指导团队撰写的博客,英文)。

【使用测试数据初始化数据库】

早先你在“DAL\SchoolInitializer.cs”中用测试数据初始化数据库,考虑为你目前新创建的类提供测试数据,现在用以下代码替换:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data.Entity;
using ContosoUniversity.Models;

namespace ContosoUniversity.DAL
{
public class SchoolInitializer : DropCreateDatabaseIfModelChanges<SchoolContext>
{
protected override void Seed(SchoolContext context)
{
var students = new List<Student>
{
new Student { FirstMidName = "Carson", LastName = "Alexander", EnrollmentDate = DateTime.Parse("2005-09-01") },
new Student { FirstMidName = "Meredith", LastName = "Alonso", EnrollmentDate = DateTime.Parse("2002-09-01") },
new Student { FirstMidName = "Arturo", LastName = "Anand", EnrollmentDate = DateTime.Parse("2003-09-01") },
new Student { FirstMidName = "Gytis", LastName = "Barzdukas", EnrollmentDate = DateTime.Parse("2002-09-01") },
new Student { FirstMidName = "Yan", LastName = "Li", EnrollmentDate = DateTime.Parse("2002-09-01") },
new Student { FirstMidName = "Peggy", LastName = "Justice", EnrollmentDate = DateTime.Parse("2001-09-01") },
new Student { FirstMidName = "Laura", LastName = "Norman", EnrollmentDate = DateTime.Parse("2003-09-01") },
new Student { FirstMidName = "Nino", LastName = "Olivetto", EnrollmentDate = DateTime.Parse("2005-09-01") }
};
students.ForEach(s => context.Students.Add(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.Add(s));
context.SaveChanges();

var departments = new List<Department>
{
new Department { Name = "English", Budget = 350000, StartDate = DateTime.Parse("2007-09-01"), InstructorID = 1 },
new Department { Name = "Mathematics", Budget = 100000, StartDate = DateTime.Parse("2007-09-01"), InstructorID = 2 },
new Department { Name = "Engineering", Budget = 350000, StartDate = DateTime.Parse("2007-09-01"), InstructorID = 3 },
new Department { Name = "Economics", Budget = 100000, StartDate = DateTime.Parse("2007-09-01"), InstructorID = 4 }
};
departments.ForEach(s => context.Departments.Add(s));
context.SaveChanges();

var courses = new List<Course>
{
new Course { CourseID = 1050, Title = "Chemistry", Credits = 3, DepartmentID = 3, Instructors = new List<Instructor>() },
new Course { CourseID = 4022, Title = "Microeconomics", Credits = 3, DepartmentID = 4, Instructors = new List<Instructor>() },
new Course { CourseID = 4041, Title = "Macroeconomics", Credits = 3, DepartmentID = 4, Instructors = new List<Instructor>() },
new Course { CourseID = 1045, Title = "Calculus", Credits = 4, DepartmentID = 2, Instructors = new List<Instructor>() },
new Course { CourseID = 3141, Title = "Trigonometry", Credits = 4, DepartmentID = 2, Instructors = new List<Instructor>() },
new Course { CourseID = 2021, Title = "Composition", Credits = 3, DepartmentID = 1, Instructors = new List<Instructor>() },
new Course { CourseID = 2042, Title = "Literature", Credits = 4, DepartmentID = 1, Instructors = new List<Instructor>() }
};
courses.ForEach(s => context.Courses.Add(s));
context.SaveChanges();

courses[0].Instructors.Add(instructors[0]);
courses[0].Instructors.Add(instructors[1]);
courses[1].Instructors.Add(instructors[2]);
courses[2].Instructors.Add(instructors[2]);
courses[3].Instructors.Add(instructors[3]);
courses[4].Instructors.Add(instructors[3]);
courses[5].Instructors.Add(instructors[3]);
courses[6].Instructors.Add(instructors[3]);
context.SaveChanges();

var enrollments = new List<Enrollment>
{
new Enrollment { StudentID = 1, CourseID = 1050, Grade = 1 },
new Enrollment { StudentID = 1, CourseID = 4022, Grade = 3 },
new Enrollment { StudentID = 1, CourseID = 4041, Grade = 1 },
new Enrollment { StudentID = 2, CourseID = 1045, Grade = 2 },
new Enrollment { StudentID = 2, CourseID = 3141, Grade = 4 },
new Enrollment { StudentID = 2, CourseID = 2021, Grade = 4 },
new Enrollment { StudentID = 3, CourseID = 1050 },
new Enrollment { StudentID = 4, CourseID = 1050, },
new Enrollment { StudentID = 4, CourseID = 4022, Grade = 4 },
new Enrollment { StudentID = 5, CourseID = 4041, Grade = 3 },
new Enrollment { StudentID = 6, CourseID = 1045 },
new Enrollment { StudentID = 7, CourseID = 3141, Grade = 2 },
};
enrollments.ForEach(s => context.Enrollments.Add(s));
context.SaveChanges();

var officeAssignments = new List<OfficeAssignment>
{
new OfficeAssignment { InstructorID = 1, Location = "Smith 17" },
new OfficeAssignment { InstructorID = 2, Location = "Gowan 27" },
new OfficeAssignment { InstructorID = 3, Location = "Thompson 304" },
};
officeAssignments.ForEach(s => context.OfficeAssignments.Add(s));
context.SaveChanges();
}
}
}

就像你在第一章中看到的一样,此代码大部分创建实体实例并把简单的数据传入属性用作测试;然而请注意Course——这个和Instructor存在多对多关系的类是如何被处理的:

var courses = new List
{
new Course { CourseID = 1050, Title = "Chemistry", Credits = 3, DepartmentID = 3, Instructors = new List() },
...
};
courses.ForEach(s => context.Courses.Add(s));
context.SaveChanges();

courses[0].Instructors.Add(instructors[0]);
...
context.SaveChanges();

当你创建了一个Course对象,你使用了Instructors = new List()初始化了Instructors导航属性;这使得你可以通过Instructors.Add方法把Instructor加入与之相关的Course中;如果你不创建这个空的Instructors列表,你就不能添加Instructor;因为Instructors是未赋值的(null),不存在Add方法。

注意:记住当你把这个程序发布到服务器上,你必须移除全部初始化数据库的测试数据的代码。

【删除与重建数据库】

现在你可以运行这个网站程序,在Index页中选择“Student”选项卡:

Students_index_page_with_formatted_date

该代码看上去与之前无异,不过数据库其实已经重建了。

如果你不是选择了Students选项卡,那么取而代之的你看到的是以下的错误——你必须打开Server Explorer,关闭数据库连接然后再次切换到Index页的Students选项卡中:

School.sdf_in_use_error_message

在查看了Index页的Students数据,在Server Explorer中打开数据库,展开Tables节点你将看到数据库全部被重建了。

Server_Explorer_showing_School_tables

除了EdMetaData数据表,你还看到一个你没有显式创建的数据表CourseInstructor——正如早先解释的一样——这是一个在“Instructor”和“Course”之间“多对多”的连接表。右键单击此表,选择Show Table Data验证确认的确里边存在着作为你添加到Course.Instructors中的数据。

Table_data_in_CourseInstructor_table

你现在已经有了一个较为复杂的数据模型和与之对应的数据库,在后续教程中你将学到更多有关处理“关系数据”的不同方法。

关于其它EntityFramework资源您可以本系列最后一篇末尾处找到。

posted @ 2012-04-27 17:15  Serviceboy  阅读(1016)  评论(0编辑  收藏  举报