共用方式為


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

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

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

在本教學課程中,您:

  • 瞭解如何將繼承對應至資料庫
  • 建立 Person 類別
  • 更新 Instructor 和 Student
  • 將人員新增至模型
  • 建立和更新移轉
  • 測試實作
  • 部署至 Azure

必要條件

將繼承對應至資料庫

Instructor資料模型中的 SchoolStudent 類別有數個相同屬性:

Student_and_Instructor_classes

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

Student_and_Instructor_classes_deriving_from_Person_class

有幾種方式可以在資料庫中表示此繼承結構。 您可以有一個 Person 數據表,其中包含單一表格中學生和講師的相關信息。 某些數據行只能套用至講師 (HireDate) ,有些則僅適用於學生 (EnrollmentDate) ,有些則適用於 (LastName) FirstName 。 一般而言,您會有 一個鑒別子 數據行來指出每個數據列所代表的類型。 例如,鑑別子資料行的 "Instructor" 代表講師,而 "Student" 代表學生。

每個數據表hierarchy_example

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

替代方法是讓資料庫看起來更像繼承結構。 例如,您只能有數據表中 Person 的名稱欄位,而且有個別 Instructor 的 和 Student 具有日期欄位的數據表。

每個數據表的數據表type_inheritance

針對每個實體類別建立資料庫數據表的模式稱為 每個類型 數據表, (TPT) 繼承。

還有另一個選項是將所有的非抽象類型對應至個別資料表。 類別的所有屬性 (包括繼承的屬性) 都會對應至對應資料表的資料行。 這個模式稱為一實體類一表 (TPC) 繼承。 如果您如先前所示實作、 Student和 類別的 PersonTPC 繼承,Student則和 Instructor 數據表在實作繼承之後看起來Instructor與之前不同。

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

本教學課程將示範如何實作 TPH 繼承。 TPH 是 Entity Framework 中的預設繼承模式,因此您只需要建立Person類別、將和 Student 類別變更Instructor為衍生自 Person、將新類別新增至 DbContext,然後建立移轉。 (如需如何實作其他繼承模式的資訊,請參閱 MSDN Entity Framework 檔中的對應數據表個別 類型 (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 中,從 Person 類別衍生 Instructor 類別,並移除索引鍵和名稱欄位。 程式碼看起來應該如下列範例所示:

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 中,新增 DbSet 實體類型的屬性 Person

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

這就是 Entity Framework 為了設定單表繼承而必須執行的所有工作。 如您所見,當資料庫更新時,它會有數據表 Person 來取代 StudentInstructor 數據表。

建立和更新移轉

在 [套件管理員控制台] (PMC) 中,輸入下列命令:

Add-Migration Inheritance

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

無法卸除物件 『dbo。Instructor',因為它是由 FOREIGN KEY 條件約束所參考。

開啟 移轉<timestamp>_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,因為學生資料列不會有雇用日期。
    • 新增暫存欄位,它將用來更新指向學生的外部索引鍵。 當您將學生複製到 Person 數據表時,他們會收到新的主鍵值。
  • 將 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 會在自動執行移轉時在該處收到相同的錯誤。 如果您想要針對移轉錯誤進行疑難解答,最佳資源是其中一個 Entity Framework 論壇或 StackOverflow.com。

測試實作

執行月臺並嘗試各種頁面。 一切項目的運作與之前一樣。

[伺服器總管] 中, 展開 [數據連線\SchoolContext ],然後展開 [ 數據表],您會看到 StudentInstructor 數據表已由 Person 數據表取代。 展開 Person 數據表,您會看到它具有所有在 StudentInstructor 數據表中使用的數據行。

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

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

School_database_diagram

部署至 Azure

本節要求您已完成本教學課程系列第 3 部分、排序、篩選和分頁的選擇性部署應用程式至 Azure 一節。 如果您在本機專案中刪除資料庫來解決移轉錯誤,請略過此步驟;或建立新的月台和資料庫,並部署至新的環境。

  1. 在 Visual Studio 的 [方案總管] 中以滑鼠右鍵按一下專案,再選取內容功能表中的 [發佈]

  2. 按一下 [發佈]。

    Web 應用程式會在預設瀏覽器中開啟。

  3. 測試應用程式以確認其運作正常。

    第一次執行頁面來存取資料庫時,Entity Framework 會執行所有 Up 移轉方法,讓資料庫與目前數據模型一起更新。

取得程式碼

下載已完成的專案

其他資源

您可以在 ASP.NET 數據存取 - 建議的資源中找到其他 Entity Framework 資源的連結。

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

後續步驟

在本教學課程中,您:

  • 瞭解如何將繼承對應至資料庫
  • 建立 Person 類別
  • 更新 Instructor 和 Student
  • 已將人員新增至模型
  • 已建立和更新移轉
  • 測試實作
  • 部署至 Azure

請前進到下一篇文章,以了解當您超越使用 Entity Framework Code First 開發 ASP.NET Web 應用程式的基本概念時,瞭解這些主題很有用。