共用方式為


教學課程:在 ASP.NET MVC 5 應用程式中使用 EF 實作繼承

在上一教學課程中,您處理了並發例外狀況。 本教學課程將示範如何在資料模型中實作繼承。

在物件導向程式設計中,可以使用繼承來促進程式碼重複使用。 在本教學課程中,您將變更 InstructorStudent 類別,讓它們衍生自 Person 基底類別,而此基底類別包含講師和學生通用的屬性,例如 LastName。 您不會新增或變更任何網頁,但是您將變更一些程式碼,這些變更將會自動反映在資料庫中。

在本教學課程中,您已:

  • 學習將繼承映射到資料庫
  • 建立 Person 類別
  • 更新 Instructor 和 Student
  • 將人員加入模型中
  • 建立和更新遷移
  • 測試實作
  • 部署至 Azure

必要條件

將繼承對應至資料庫

School 資料模型中的 InstructorStudent 類別具有幾個相同的屬性:

Student_and_Instructor_classes

假設您想要針對 InstructorStudent 實體所共用的屬性消除多餘的程式碼。 或者,您想要撰寫服務,以便用來格式化名稱,而無需在意名稱是來自講師還是學生。 您可以建立一個僅包含這些共用屬性的 Person 基本類別,然後使 InstructorStudent 實體繼承自該基類,如下圖所示:

Student_and_Instructor_classes_deriving_from_Person_class

有幾種方式可以在資料庫中表示此繼承結構。 您擁有的 Person 資料表可能在單一資料表中同時包含學生和講師的資訊。 有些欄位只適用於教師 (HireDate),有些僅適用於學生 (EnrollmentDate),有些則同時適用 (LastNameFirstName)。 通常,您將有一個鑑別器列來指示每行代表哪種類型。 例如,鑑別子資料行的 "Instructor" 代表講師,而 "Student" 代表學生。

Table-per-hierarchy_example

從單一資料庫資料表產生實體繼承結構的這種模式稱為一階層一表 (TPH) 繼承。

替代方法是讓資料庫看起來更像繼承結構。 比方說,您可以只在 Person 資料表中包含名稱欄位,而在個別的 InstructorStudent 資料表中包含日期欄位。

Table-per-type_inheritance

這種為每個實體類別建立資料庫表的模式稱為每類型表 (TPT) 繼承。

還有另一個選項是將所有的非抽象類型對應至個別資料表。 類別的所有屬性 (包括繼承的屬性) 都會對應至對應資料表的資料行。 此模式稱為一具象類別一表 (TPC) 繼承。 如果您如先前所示實作 PersonStudentInstructor 類別的 TPC 繼承,則 StudentInstructor 資料表在實作繼承之後看起來與以前相同。

TPC 和 TPH 繼承模式通常在實體框架中提供比 TPT 繼承模式更好的效能,因為 TPT 模式可能會導致複雜的連結查詢。

本教學課程將示範如何實作 TPH 繼承。 TPH 是實體框架中的預設繼承模式,因此您所要做的就是建立一個 Person 類別,將 InstructorStudent 類別變更為衍生自 Person,將新類別新增至 DbContext,然後建立遷移。 (有關如何實現其他繼承模式的信息,請參閱 MSDN 實體框架文件中的映射每類型表 (TPT) 繼承映射每具體類表 (TPC) 繼承。)

建立 Person 類別

Models 資料夾中,建立 Person.cs 並將範本程式碼替換為以下程式碼:

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
    public abstract class Person
    {
        public int ID { get; set; }

        [Required]
        [StringLength(50)]
        [Display(Name = "Last Name")]
        public string LastName { get; set; }
        [Required]
        [StringLength(50, ErrorMessage = "First name cannot be longer than 50 characters.")]
        [Column("FirstName")]
        [Display(Name = "First Name")]
        public string FirstMidName { get; set; }

        [Display(Name = "Full Name")]
        public string FullName
        {
            get
            {
                return LastName + ", " + FirstMidName;
            }
        }
    }
}

更新 Instructor 和 Student

現在更新 Instructor.csStudent.cs 以繼承 Person.sc 的值。

Instructor.cs 中,從 Instructor 類別衍生 Person 類別並刪除鍵和名稱欄位。 程式碼看起來應該如下列範例所示:

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

namespace ContosoUniversity.Models
{
    public class Instructor : Person
    {
        [DataType(DataType.Date)]
        [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
        [Display(Name = "Hire Date")]
        public DateTime HireDate { get; set; }

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

Student.cs 進行類似的更改。 Student 類別將類似於以下範例:

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

namespace ContosoUniversity.Models
{
    public class Student : Person
    {
        [DataType(DataType.Date)]
        [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
        [Display(Name = "Enrollment Date")]
        public DateTime EnrollmentDate { get; set; }

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

將人員加入模型中

SchoolContext.cs 中,為 Person 實體類型新增一個 DbSet 屬性:

public DbSet<Person> People { get; set; }

這就是 Entity Framework 為了設定單表繼承而必須執行的所有工作。 正如您將看到的,當資料庫更新時,它將有一個 Instructor 表來代替 PersonStudent 表。

建立和更新遷移

請在套件管理員主控台 (PMC) 中輸入下列命令:

Add-Migration Inheritance

在 PMC 中執行 Update-Database 命令。 該命令此時將失敗,因為我們有遷移不知道如何處理的現有資料。 您會收到以下錯誤訊息:

無法刪除物件“dbo.Instructor”,因為它由 FOREIGN KEY 約束引用。

開啟 Migrationstime<stamp>_Inheritance.cs 並將該 Up 方法替換為以下程式碼:

public override void Up()
{
    // Drop foreign keys and indexes that point to tables we're going to drop.
    DropForeignKey("dbo.Enrollment", "StudentID", "dbo.Student");
    DropIndex("dbo.Enrollment", new[] { "StudentID" });

    RenameTable(name: "dbo.Instructor", newName: "Person");
    AddColumn("dbo.Person", "EnrollmentDate", c => c.DateTime());
    AddColumn("dbo.Person", "Discriminator", c => c.String(nullable: false, maxLength: 128, defaultValue: "Instructor"));
    AlterColumn("dbo.Person", "HireDate", c => c.DateTime());
    AddColumn("dbo.Person", "OldId", c => c.Int(nullable: true));

    // Copy existing Student data into new Person table.
    Sql("INSERT INTO dbo.Person (LastName, FirstName, HireDate, EnrollmentDate, Discriminator, OldId) SELECT LastName, FirstName, null AS HireDate, EnrollmentDate, 'Student' AS Discriminator, ID AS OldId FROM dbo.Student");

    // Fix up existing relationships to match new PK's.
    Sql("UPDATE dbo.Enrollment SET StudentId = (SELECT ID FROM dbo.Person WHERE OldId = Enrollment.StudentId AND Discriminator = 'Student')");

    // Remove temporary key
    DropColumn("dbo.Person", "OldId");

    DropTable("dbo.Student");

    // Re-create foreign keys and indexes pointing to new table.
    AddForeignKey("dbo.Enrollment", "StudentID", "dbo.Person", "ID", cascadeDelete: true);
    CreateIndex("dbo.Enrollment", "StudentID");
}

此程式碼負責下列資料庫更新工作:

  • 移除指向 Student 資料表的外部索引鍵條件約束和索引。

  • 將 Instructor 資料表重新命名為 Person,並對其進行所需的變更來儲存 Student 資料:

    • 針對學生新增可為 null 的 EnrollmentDate。
    • 新增 Discriminator 資料行,以指出資料列適用於學生或講師。
    • 使 HireDate 成為可為 Null,因為學生資料列不會有雇用日期。
    • 新增暫存欄位,它將用來更新指向學生的外部索引鍵。 當您將學生複製到人員表中時,他們將獲得新的主鍵值。
  • 將 Student 資料表中的資料複製到 Person 資料表。 這會導致學生獲指派新的主索引鍵值。

  • 修正指向學生的外部索引鍵值。

  • 重新建立外部索引鍵條件約束和索引,現在將它們指向 Person 資料表。

(如果您使用 GUID 而不是整數作為主索引鍵類型,學生的主索引鍵值將無需變更,而且可能已省略其中幾個步驟。)

再次執行 update-database 命令。

(在生產系統中,您將對 Down 方法進行相應的更改,以防您必須使用該方法返回到先前的資料庫版本。在本教學課程中,您將不會使用 Down 方法。)

注意

遷移資料和進行架構變更時可能會出現其他錯誤。 如果出現無法解決的遷移錯誤,您可以透過變更 Web.config 檔案中的連接字串或刪除資料庫來繼續學習本教學課程。 最簡單的方法是在 Web.config 檔案中重新命名資料庫。 例如,將資料庫名稱變更為 ContosoUniversity2,如下例所示:

<add name="SchoolContext" 
    connectionString="Data Source=(LocalDb)\v11.0;Initial Catalog=ContosoUniversity2;Integrated Security=SSPI;" 
    providerName="System.Data.SqlClient" />

使用新資料庫時,無需遷移資料,且 update-database 命令更有可能順利完成。 有關如何刪除資料庫的說明,請參閱如何從 Visual Studio 2012 中刪除資料庫。 如果您採用此方法來繼續本教學課程,請跳過本教學課程末端的部署步驟或部署到新網站和資料庫。 如果您將更新部署到已部署到的相同站點,則 EF 在自動執行遷移時將出現相同的錯誤。 如果您想解決遷移錯誤,最好的資源是實體框架論壇或 StackOverflow.com 之一。

測試實作

運行該網站並嘗試各個頁面。 一切項目的運作與之前一樣。

伺服器資源管理器中,展開 Data Connections\SchoolContext,然後展開 Tables,您會看到 StudentInstructor 表已被 Person 表取代。 展開 人員 表,您會看到它包含以前位於 學生教師 表中的所有欄位。

以滑鼠右鍵按一下 Person 資料表,然後按一下 [顯示資料表資料] 以查看鑑別子資料行。

下圖說明了新 School 資料庫的結構:

School_database_diagram

部署至 Azure

本部分要求您完成本教學課程系列第 3 部分排序、篩選和分頁中的可選將應用程式部署到 Azure 部分。 如果您有透過刪除本機專案中的資料庫解決的遷移錯誤,請跳過此步驟;或建立新的網站和資料庫,並部署到新環境。

  1. 在 Visual Studio 中,以滑鼠右鍵按一下解決方案資源管理器中的項目,然後從上下文功能表中選擇發佈

  2. 按一下發行

    Web 應用程式將在您的預設瀏覽器中開啟。

  3. 測試應用程式以驗證其是否正常運作。

    第一次執行存取資料庫的頁面時,實體框架會執行使資料庫與目前資料模型保持同步所需的所有遷移 Up 方法。

取得程式碼

下載已完成的項目

其他資源

可以在 ASP.NET 資料存取 - 推薦資源中找到其他實體框架資源的連結。

有關此繼承結構和其他繼承結構的詳細信息,請參閱 MSDN 上的 TPT 繼承模式TPH 繼承模式。 在下一個教學課程中,您將了解如何處理各種相對進階的 Entity Framework 案例。

下一步

在本教學課程中,您已:

  • 學會了將繼承映射到資料庫
  • 建立 Person 類別
  • 更新 Instructor 和 Student
  • 將人物加入模型中
  • 建立和更新遷移
  • 測試實作
  • 部署到 Azure

請繼續閱讀下一篇文章,以了解當您超越使用 Entity Framework Code First 開發 ASP.NET Web 應用程式的基礎知識時需要注意的有用主題。