[渣譯文] 使用 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 web

譯文版權全部,謝絕全文轉載——但您能夠在您的網站上添加到該教程的連接。數據庫

在以前的教程中,您已經學習瞭如何處理併發異常。在本教程中,咱們將介紹如何實現繼承。編程

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

映射繼承到數據庫的選項

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

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

###mvc

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

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

替代方法是使用看起來更像繼承結構的數據庫,例如,你能夠只在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平臺及工具團隊的高級程序員,做家。

相關文章
相關標籤/搜索