MVC中使用EF(4):ASP.NET MVC 創建更復雜的數據模型

爲ASP.NET MVC 創建更復雜的數據模型

By Tom Dykstra|July 30, 2013
Translated by litdwg

Contoso University示例網站演示如何使用Entity Framework 5創建ASP.NET MVC 4應用程序。Entity Framework有三種處理數據的方式: Database FirstModel 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.

目前在指南中使用的數據模型由三個實體組成。這一節你將添加更多實體和它們之間的關係,也將通過格式指明、驗證、數據庫映射規則對數據模型進行定製。你將學習兩種定製數據模型的方式:在實體類添加特性或在數據上下文類添加代碼。

完成之後的實體及其關係如下:

School_class_diagram

使用特性定製數據模型

本小節你將學習如何使用格式指明、驗證、數據映射規則等特性定製數據模型。隨後將完成所需要的數據模型。

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 模型的視圖中關於日期是否不再顯示時間.

Students_index_page_with_formatted_date

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,提示信息立即就顯示了.

 client side val error

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

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

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 特性

在 InstructorOfficeAssignment 實體之間是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

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導航屬性.
    public int DepartmentID { get; set; }
    public virtual Department Department { get; set; }
  • A course 有多個 student註冊登記,因此 Enrollments 導航屬性是一個集合:
    public virtual ICollection<Enrollment> Enrollments { get; set; }
  • A course 可能由 instructors講授, 因此 Instructors 導航屬性是集合:
    public virtual ICollection<Instructor> Instructors { get; set; }

創建Department Entity

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會爲數據庫的列選擇適當的類型。這裏只是進一步明確了數據庫要使用的類型。

外鍵和導航屬性

外鍵和導航屬性如下:

  • 一個部門可能有一個管理員,管理員也是老師,因此這裏有如下外鍵和屬性:
    public int? InstructorID { get; set; }
    public virtual Instructor Administrator { get; set; }
  • A department 可以有多門 courses, 因此有一個 Courses 導航屬性
    public virtual ICollection<Course> Courses { get; set; }

注意:EF在多對多關係中刪除不爲空的外鍵時採用瀑布模式。這可能導致循環瀑布刪除,從而引發異常:“引用關係導致循環引用”。如果業務規則要求InstructorID 不爲空,可使用如下代碼禁用瀑布刪除:

modelBuilder.Entity().HasRequired(d => d.Administrator).WithMany().WillCascadeOnDelete(false);

修改 Student Entity

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導航屬性:
    public int CourseID { get; set; }
    public virtual Course Course { get; set; }
  • 一條登記信息對應一個學生,因此有 StudentID 外鍵屬性 Student導航屬性:
    public int StudentID { get; set; }
    public virtual Student Student { get; set; }

多對多關係

在 Student 和Course 實體之間是多對多關係,  Enrollment實體的作用是作爲二者之間的連接表. 也就是說 Enrollment 表包含額外的外鍵之外的信息(本例中,主鍵和Grade屬性).

下圖顯示了這些實體之間的關係. (圖是使用 Entity Framework Power Tools生成的; )

Student-Course_many-to-many_relationship

由兩個一對多的關係組成.

如果 Enrollment 不需要保存Grade信息,只需要CourseID and StudentID. 就成了單純的連接表不包含額外信息, 那Enrollment 實體就無需存在了.  Instructor 和Course 直接建立多對多的關係:

Instructor-Course_many-to-many_relationship

但數據庫中還是需要連接表的:

Instructor-Course_many-to-many_relationship_tables

Entity Framework自動創建了 CourseInstructor 表, 在讀取和更新時你會間接用到它。

實體圖顯示了實體之間的關係

 Entity Framework Power Tools創建的完整的 School 模型圖.

School_data_model_diagram

實體間有一對一關係、一對一或零關係,一對多關係和多對多關係.

向 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相關信息.

Table_data_in_CourseInstructor_table

總結

你已經創建了更加複雜的數據模型和相應的數據庫。隨後你將學習更多訪問關聯數據的方法。

Entity Framework 相關資源請查看 ASP.NET Data Access Content Ma

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章