全文目錄:Contoso 大學 - 使用 EF Code First 創建 MVC 應用
4 – 創建更加復雜的數據模型
在前面的課程中,你已經創建了一個簡單的由三個實體組成的數據模型。在這個課程中,你將要增加更多的實體,以及關系,使用數據標注特性來控制模型類的行為。
在完成的時候,實體類表示的完整數據模型如下所示:
4-1 使用特性控制格式、驗證以及數據庫映射
在這一節中,你將會看到如何使用特性來控制數據模型的格式化、驗證以及數據庫映射。然后在后繼的節中,將要通過為已經創建的類、新創建的類增加特性,來創建完整的 School 數據模型。
4-1-1 DisplayFormat 特性
對於學生的注冊日期來說,雖然你只關心注冊的日期,但是現在的頁面在日期之后還顯示了時間。通過使用數據標注特性,可以通過一點代碼就可以在所有的地方修補這個問題。看一下示例,你就可以為 Student 類的 EnrollmentDate 屬性增加一個特性了。
在 Models\Student.cs ,在開始部分為命名空間 System.ComponentModel.DataAnnotations 增加一個 using 語句,然后在 EnrollmentDate 屬性上增加一個 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; } [DisplayFormat(DataFormatString = "{0:d}", ApplyFormatInEditMode = true)] public DateTime EnrollmentDate { get; set; } public virtual ICollection<Enrollment> Enrollments { get; set; } } }
格式化串指定了在顯示這個屬性的時候僅僅使用短日期格式。ApplyFormatInEditMode 指定即使在將這個屬性的值顯示在文本框中進行編輯的時候也應用這個特性。( 有些字段不需要這些特殊設置,比如,在文本框中編輯貨幣的時候,就不會希望出現貨幣符號 )。
再次運行程序,你會注意到注冊時間不再是長日期格式了,如果你查看其他的學生頁面也會看到同樣的結果。
4-1-2 MaxLength 特性
還可以通過特性來指定數據驗證規則和錯誤提示信息。假設你希望用戶在輸入名字的時候不能超過 50 個字符長度,對於這個限制,可以為 LastName 屬性和 FirstName 屬性增加 MaxLength 特性,如下所示.
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; } } }
如果用戶為 LastName 輸入一個超長的名字,默認的錯誤信息就會顯示出來,如果輸入一個超長的 FirstName,你定制的錯誤信息會被顯示出來。
運行程序,輸入兩個超過 50 個字符的超長名字,然后點擊 Create 來查看錯誤信息。( 但是你需要輸入一個正確的日期來通過驗證 )
對於字符串屬性總是指定最大長度是個好主意。如果沒有這樣做,在 CodeFirst 創建數據庫的時候,相關的數據庫中的列寬度就會被設置為字符串的最大長度,這可能會導致低效的數據庫結構。
4-1-3 Column 特性
還可以通過特性來控制你的模型類及屬性如何映射到數據庫。假設你已經使用 FirstMidName 來表示名字,因為屬性也包含了中間名,但是你希望數據庫中的列名為 FirstName,因為編寫本地查詢的用戶習慣使用這個名字,要完成這種映射,你可以使用 Column 特性。
Column 特性在數據庫創建的時候被使用。Student 的屬性 FirstMidName 在數據庫中將被命名為 FirstName。也就是說,當你的代碼使用 Student.FirstMidName 的時候,數據將來自數據庫中 Student 表的 FirstName 列 ( 如果你沒有指定列名,默認將與屬性同名 )
為 FirstMidName 增加列名的映射特性,如下所示。
[Column("FirstName")] public string FirstMidName { get; set; }
再次運行程序,你會發現沒有任何變化。( 不能僅僅運行程序,然后查看主頁。你需要選擇 Student 的 Index 頁面,因為這將導致對數據庫的訪問,這會使數據庫首先被自動刪除,然后重建 )。實際上,如果你在服務器資源管理器中打開數據庫,展開 Student 表,就會發現列名 FirstName。
在屬性窗口,你還會發現與此列相關的字段被定義為 50 個字符長度,與你在前面增加的 MaxLength 特性相一致。
在另外一些情況下,你還可以通過方法調用進行映射,后面就會看到。
在后面的段落中,你使用到更多的數據標注來擴展學校數據模型。在每節中,你要為實體創建一個類,或者修改前面創建的類。
注意:如果在完成所有的實體之間,你試圖編譯,可能會遇到編譯錯誤。
4-2 創建 Instructor 實體
創建 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 實體多數屬性是類似的。在這個系列后面 實現繼承 的部分,會對代碼進行重構,使用繼承來消除重復。
4-2-1 Required 和 Display 特性
在 LastName 屬性上的特性指定這是一個必須字段,在編輯的文本框上的提示文本應該為 “Last Name” ( 代替屬性名稱,屬性名中間沒有空格 ),而且字段的值不能超過 50 個字符。
[Required(ErrorMessage = "Last name is required.")] [Display(Name="Last Name")] [MaxLength(50)] public string LastName { get; set; }
4-2-2 FullName 計算屬性
FullName 是計算屬性,返回其他兩個屬性計算出的結果。因此它只有 get 訪問方法,而且在數據庫中沒有名為 FullName 的字段。
public string FullName { get { return LastName + ", " + FirstMidName; } }
4-2-3 Courses 和 OfficeAssignment 導航屬性
Courses 和 OfficeAssignment 屬性是導航屬性,像我們在前面說明的,它們典型地被定義為虛擬的 virtual,以便 EF 的延遲加載特性可以提供幫助,另外,如果導航屬性可以包含多個實體,它的類型必須為 ICollection。
一個教師可以教授多門課程,所以 Courses 被定義為 Course 的集合。另一方面,一個教師僅有一間辦公室,所以 OfficeAssignment 被定義為單個的 OfficeAssignment 實體 ( 如果沒有辦公室的話,可能為 null )。
public virtual ICollection<Course> Courses { get; set; } public virtual OfficeAssignment OfficeAssignment { get; set; }
4-3 創建 OfficeAssignment 實體
創建 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; } } }
4-3-1 Key 特性
在 Instructor 和 OfficeAssignment 實體之間存在一對一或者一對零的關系。一個辦公室分配僅僅存在於被分配給教師的時候。進而,它的主鍵也應該是 Instructor 實體的外鍵。但是 EF 並不能自動將 InstructorID 作為分配的主鍵,因為名字不符合約定,既不是 ID ,也不是類名加上 ID,因此,使用 Key 特性來標識這是一個主鍵。
[Key] public int InstructorID { get; set; }
在主鍵的屬性名既不是 Id 也不是類名加上 ID 的時候,可以通過 Key 特性。
4-3-2 Instructor 導航屬性
Instructor 實體有一個可空的 OfficeAssignment 導航屬性 ( 因為教師可能沒有分配辦公室 ),在 OfficeAssignment 實體上則有一個不可為空的 Instructor 導航屬性 ( 因為辦公室分配不可能在沒有教師的情況下存在 ),當 Instructor 實體關聯到 OfficeAssignment 實體的時候,它們可以通過導航屬性相互引用對方。
4-4 修改 Course 實體
在 Models\Course.cs, 將原來的代碼替換為如下代碼。
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; } } }
4-4-1 DatabaseGenerated 特性
在 CourseID 屬性上的DatabaseGenerated 特性,使用了 None 參數,指定主鍵將由用戶提供,而不是通過數據庫自動生成。
[DatabaseGenerated(DatabaseGeneratedOption.None)] [Display(Name = "Number")] public int CourseID { get; set; }
默認情況下,EF 假設主鍵由數據庫自動生成。這可以用於大多數場景下,但是,對於 Course 實體來說,你希望使用自定義的課程編號,例如:1000 序列表示一個系,2000 序列表示另外一個系等等。
4-4-2 外鍵和導航屬性
在 Course 實體中的外鍵屬性和導航屬性,反射了如下的關系:
- 一個課程屬於一個系,所以存在一個 DepartmentID 外鍵和一個 Department 導航屬性:
public int DepartmentID { get; set; } public virtual Department Department { get; set; }
- 一個課程可以被多個學生注冊,所以存在一個 Enrollments 導航屬性
public virtual ICollection Enrollments { get; set; }
- 一個課程可能被多名教師教授,所以這里存在一個 Instructors 導航屬性。
public virtual ICollection<Instructor> Instructors { get; set; }
4-5 創建 Department 實體

創建 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; } } }
4-5-1 Column 特性
前面我們使用 Column 特性改變了默認的列名映射。在 Department 實體中,Column 特性被用來改變 SQL 數據類型映射,以便在數據庫中使用 SQL Server 的 money 數據類型 。
[Column(TypeName="money")] public decimal? Budget { get; set; }
通常並不需要,因為 EF 會基於屬性的 CLR 數據類型來選擇合適的 SQL Server 數據類型。對於 CLR 中的 decimal 類型會映射到 SQL Server 中的 decimal 類型。但在這里,你知道這個列將會保存合計,所以 money 數據類型更加合適。
4-5-2 外鍵和導航屬性
外鍵和導航屬性反映了如下關系:
- 一個系可能有或者沒有一個系主任,系主任通常是教師。進而 InstructorID 屬性包含了一個教師實體的外鍵。加在 int 類型后面的問號表示這是一個可空類型,導航屬性名為 Administrator ,實際保存 Instructor 實體的引用。
public int? InstructorID { get; set; } public virtual Instructor Administrator { get; set; }
- 一個系可能有多個課程,所以 Courses 是導航屬性。
public virtual ICollection Courses { get; set; }
注意:
默認情況下, EF 允許非空外鍵和多對多關系的級聯刪除。這可能會導致環形刪除,在你的初始化代碼運行的時候導致異常。例如,如果你沒有定義 Department.InstructorID 屬性作為可空類型,在初始化的時候,你會得到如下的異常:"The referential relationship will result in a cyclical reference that's not allowed."
4-6 修改 Student 實體
打開 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; } } }
這段代碼中僅僅增加了一些你已經見過的特性。
4-7 修改 Enrollment 實體
打開 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; } } }
4-7-1 外鍵和導航屬性
外鍵和導行屬性反映了如下的關系:
- 一條注冊信息用於單個課程,所以存在一個 CourseID 外鍵屬性和一個 Course 導航屬性。
public int CourseID { get; set; } public virtual Course Course { get; set; }
- 一條注冊信息用於一個學生, 所以存在一個 StudentID 外鍵和一個 Student 導航屬性。
public int StudentID { get; set; } public virtual Student Student { get; set; }
4-7-2 多對多關系
在學生實體和課程實體之間存在多對多的關系。在數據庫中,Enrollment 實體在兩張表之間承擔了多對多的關聯。這意味着 Enrollment 表中還包含了關聯表中額外的數據 ( 在這里, 一個主鍵和一個成績 Grade 屬性 )。
下圖展示了在實體圖中的關系。( 這張圖使用 EF 的設計器生成,創建這種圖不是這個系列關注的內容,這里僅僅用來說明 )。
每個關聯線的兩端有一個 1 和一個星 *,表示一對多的關系。
如果注冊表中不包含成績信息,那就可以僅僅包含兩個外鍵 CourseId 和 StudentId。在這中情況下,在關聯表中就沒有額外的信息 ( 或者是純的鏈接表 ),你也就完全沒有必要創建這個注冊的模型。教師 Instructor 和課程 Course 實際直接使用多對多關聯,如你所見,在它們之間沒有其他的實體。
在數據庫中需要一個關聯表,如下所示。
EF 將會自動創建 CourseInstructor 表,你可以間接地讀取或者更新其中的數據,通過讀寫 Instructor.Courses 和 Course.Instructors 導航屬性。
4-7-3 DisplayFormat 特性
在成績 Grade 屬性上的 DisplayFormat 特性指定數據如何被格式化。
[DisplayFormat(DataFormatString="{0:#.#}",ApplyFormatInEditMode=true,NullDisplayText="No grade")] public decimal? Grade { get; set; }
- 成績顯示為通過圓點分割的兩個數字,例如:3.5 或者 4.0
- 在編輯模式 ( 例如文本框中 )也需要這樣顯示。
- 如果沒有成績 ( 在 decimal 后面的問號表示這是可空類型 ),顯示 “No”
4-8 實體關系圖
下面是通過 EF 設計器創建的學校實體關系圖。
除了多對多的關聯線,以及一對多的關聯線。還可以看到在 Instructor 與 OfficeAssignment之間的一對一或一對零的關聯線,Instructor 與 Department 實體之間的零或一對多的關聯線。
4-9 定制數據庫上下文
下一步將要為 SchoolContext 增加一些新的實體,通過 Fluent 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 實體之間多對多的關系。代碼中指定了關聯表及其列名。EF CodeFirst 不需要這些代碼就可以自動為你配置關系,但是,如果沒有調用它,你會得到默認的名稱,例如對於 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.Administrator 表示。
modelBuilder.Entity<Department>()
.HasOptional(x => x.Administrator);
關於更加詳細的 Fluent API 語法說明,可以參見博客 Fluent API 。
4-10 使用測試數據初始化數據庫
在前面的課程中,你已經創建了 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 對象的時候,通過創建空的集合初始化了 Instructor 導航屬性,使用代碼 Instructors = new List()。這使得為課程相關的教師可以通過 Instructors.Add 方法加入,如果沒有創建空的集合列表,你就不能增加這些關系,因為 Instructors 屬性為 null,不能調用 Add 方法。
注意:記住,在發布應用到 Web 服務器上的時候,你必須刪除所有的在數據庫中的種子數據。
4-11 刪除和重建數據庫
重新運行,選擇 Student 頁面。
頁面像從前一樣,但是底層的數據庫已經被重建了。
如果你沒有看到學生頁面,而是看到一個錯誤頁面,提示說 School.sdf 文件正在被使用 ( 見下圖 ),你需要重新打開服務器資源管理器,然后關閉與數據庫的連接,然后重試一下。
在查看學生頁面之后,重新打開服務器資源管理器,展開表,可以看到新創建的所有表。
另外,EdmMetadata 表不是我們創建的,還有 CourseInstructor,如前所述,這是在 Instructor 和 Course 實體之間的鏈接表。
鼠標右擊 CourseInstructor 表,選擇顯示數據,可以看到通過課程 Course.Instructors 導航屬性增加的教師實體。

現在你已經獲得了一個更加復雜的數據模型以及關聯的數據庫,在后繼的課程中,你可以學到通過不同的途徑訪問關聯數據。