[渣譯文] 使用 MVC 5 的 EF6 Code First 入門 系列:為ASP.NET MVC應用程序實現繼承


這是微軟官方教程Getting Started with Entity Framework 6 Code First using MVC 5 系列的翻譯,這里是第十一篇:為ASP.NET MVC應用程序實現繼承

原文:Implementing Inheritance with the Entity Framework 6 in an ASP.NET MVC 5 Application

譯文版權所有,謝絕全文轉載——但您可以在您的網站上添加到該教程的鏈接。

在之前的教程中,您已經學習了如何處理並發異常。在本教程中,我們將介紹如何實現繼承。

在面向對象的編程中,你可以使用繼承以便重用代碼。在本教程中,您將更改Instructor和Student類,使它們從包含姓名屬性的Person基類派生。你無須改動任何WEB頁面,但你的改動會自動反映在數據庫中。

映射繼承到數據庫的選項

數據模型中的Instructor和Student類有幾個相同的屬性:

假設您想要通過共享教師和學生實體的屬性來消除冗余的代碼,或者您想要編寫一個無需關心名稱是否來自學生或教師的從而正確格式化姓名的服務。你可以創建一個包含這些共享屬性的Person基類,然后使教師和學生實體的類從基類繼承,如下圖所示:

###

在數據庫中,這種繼承結構有幾種表現形式。你可以創建一個Person數據表,包含教師和學生和學生信息的單個表,某些列可能僅適用於教師(雇佣日期),某些只適用於學生(注冊日期),某些兩者都要使用(姓、名)。通常情況下,你會有一個標識列,以指示每一行所代表的類型,例如,標識列可能使用"Instructor"來表示教師,"Student"來表示學生。

從單個數據庫表生成實體繼承結構的模式被稱為每層一表繼承模式。

替代方法是使用看起來更像繼承結構的數據庫,例如,你可以只在Person表中包含學生和教師共有的屬性,將獨有的屬性放在各自單獨的表中。

使每個實體類都建立一個數據庫表的模式成為每類型一表繼承。

但另一種選擇是將所有非抽象類型映射到單個表。所有類別的屬性,包括繼承的,都將映射到相應表中的列。這種模式被稱為每具體類一表繼承。如果您實現了Person,Student和Instructor類的具體類一表繼承,Student和Instructor數據表將和之前你看到的沒有兩樣。

每具體類一表和每層一表在實體框架中通常會提供比每類型一表更好地性能,因為每類型一表可能會導致復雜的連接查詢。

本教程將演示如何實現每層一表繼承。每層一表是實體框架默認的繼承模式。你所要做的就是創建一個Person類,修改Instructor和Student類派生自Person,將新的類添加到DbContext及創建遷移。

創建Person類

在Models文件夾中,使用下面的代碼創建Person類:

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

namespace ContosoUniversity.Models
{
    public abstract class Person
    {
        public int ID { get; set; }
        [Required]
        [Display(Name = "")]
        [StringLength(50)]
        public string LastName { get; set; }

        [Required]
        [Column("FirstName")]
        [Display(Name = "")]
        [StringLength(50)]
        public string FirstMidName { get; set; }


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

 

 

 

使Student和Instructor類繼承自Person

在Instructor類中,修改類從Person派生並刪除姓名字段,如下面的代碼:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;namespace ContosoUniversity.Models
{
    public class Instructor :Person
    {
        public int ID { get; set; }


        [DataType(DataType.Date)]   
        [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}",ApplyFormatInEditMode = true)]
        [Display(Name = "聘用日期")]
        public DateTime HireDate { get; set; }


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

同樣也對Student類進行修改:

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

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

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

 

向模型中添加Person實體類型

向SchoolContext.cs中添加一個Person實體的DbSet屬性:

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

 

就是在實體框架中實現繼承做需要的全部修改。稍后您會看到數據庫在更新后,會有一個新建的Person數據表。

創建及更新一個遷移文件

在軟件包管理器控制台中,輸入以下命令:

Add-Migration Inheritance

之后運行update-database命令,命令將失敗。因為實體框架不知道如何對我們現有的數據進行遷移,錯誤消息類似下面這樣:

打開Migrations\<時間戳>-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");
}

 

這段代碼執行了下列數據庫更新任務:

  • 刪除了指向學生數據表的外鍵約束和索引
  • 重命名Instructor表為Person表並進行了修改:
    • 為學生添加了可以為空的EnrollmentDate
    • 添加了標識列,以指示行是否為學生或教師
    • 使雇佣日期可以為空,因為學生的行不會有雇佣日期
    • 添加一個臨時字段用來更新指向學生的外鍵。當你將學生復制回Person表時他們會有一個新的主鍵值。
  • 將數據從學生表復制到Person表,這會導致學生有一個新的主鍵值
  • 修復了指向學生的外鍵值
  • 重新創建外鍵約束和索引,現在它們指向Person表

(如果你使用了GUID而不是int作為主鍵類型,學生的主鍵值不會改變,上面的幾個步驟可能被省略。)

再次運行update-database命令。

注意:您可以仍然得到一個錯誤,在進行遷移或架構更改時,如果遷移的錯誤無法解決,您可以通過更改web.config連接字符串或刪除該數據庫的方法來繼續本教程,最簡單的方法是重新命名數據庫。

測試

運行應用程序,嘗試各種操作,一切都正常運行。

在服務器資源管理器中,展開數據連接,展開SchoolContext的數據表,你會看到Person表已經替換了Student和Instructor表,打開Person表,你會看到之前的學生和教師的信息。

下面的關系圖說明了新數據庫的結構:

部署到Windows Azure

本章跳過……

總結

你現在實現了Person、Student和Instructor類的每層次一個表繼承。有關其他繼承結構的信息,請參閱TPH Inheritance PatternTPT Inheritance Pattern。在下一教程中,您將看到如何實現倉儲和單元工作模式。

作者信息

 

  Tom Dykstra - Tom Dykstra是微軟Web平台及工具團隊的高級程序員,作家。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM