翻譯:Contoso 大學 - 8 – 實現繼承


By Tom Dykstra, Tom Dykstra is a Senior Programming Writer on Microsoft's Web Platform & Tools Content Team.

原文地址:http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/implementing-inheritance-with-the-entity-framework-in-an-asp-net-mvc-application
全文目錄:Contoso 大學 - 使用 EF Code First 創建 MVC 應用

在上一次教程中,你已經能夠處理並發異常。這個教程將會展示如何在數據模型中實現繼承。
在面向對象的程序設計中,你可以通過繼承來清除冗余的代碼。在這個教程中,你將要通過修改教師 Instructor 和學生 Student 類,以便使他們從包含類似 LastName 屬性的 Person 類中派生。對於 Web 頁面不需要任何改動,你需要修改一點代碼,這些修改將會被自動反射到數據庫中。

8-1  單表繼承 (Table-per-Hierarchy) 對類型表 (Table-per-Type) 繼承


在面向對象的程序設計中,你可以通過對相關的類使用繼承來使得工作更加簡單。例如,教師 Instructor 和學生 Student 類在學校 School 數據模型中共享多個屬性,帶來了冗余的代碼。

假設你希望清除在教師 Instructor 和學生 Student 之間所共享的屬性帶來的冗余代碼。可以創建一個 Person 基類,其中僅僅包含他們共享的屬性,然后,使得教師 Instructor 和學生 Student 類從 Person 基類派生,如下圖所示。

 

在數據庫中這種繼承結構可以有多種表現形式,可以創建一個名為 Person 的表,在這個獨立的表中包含教師和學生所有的信息。既包括他們獨自擁有的屬性 ( 例如教師的 HireDate ,以及學生的 EnrollmentDate ),也包括它們共有的屬性 ( 例如 LastName, FirstName )。通常你還需要一個用於識別當前類型的列 discriminator 來標識當前行的類型。( 在這里,標識列的內容為 Instructor 來表示教師,Student 來表示學生 )

使用單個數據庫表來生成實體繼承結構的模式稱為單表繼承模式 TPH (table-per-hierarchy )。
另外一種方式是使得數據庫看起來類似繼承結構。例如,在 Person 表中僅僅包含他們共有的名字屬性,而將不同的時間分別保存到獨立的 Instructor 和 Student 表中。

這種每種實體類對應一張數據庫表的模式稱為類型表 TPT  繼承 (table per type )。
在 EF 中,TPH 繼承比 TPT 繼承有更好的性能,因為 TPT 繼承需要復雜的連接查詢。這個教程演示如何實現 TPH 繼承。你需要完成如下的步驟:

  • 創建 Person 類,將 Instructor 和 Student 類從 Person 類中派生
  • 在數據庫上下文類中增加模型到數據庫的映射代碼
  • 將項目中的 InstructorID 和 StudentID 修改為使用 PersonID.

8-2  創建 Person 類

在 Model 文件夾中,創建 Person.cs ,使用下面的代碼替換原有的代碼。

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

namespace ContosoUniversity.Models
{
    public abstract class Person
    {
        [Key]
        public int PersonID { get; set; }

        [Required(ErrorMessage = "Last name is required.")]
        [Display(Name="Last Name")]
        [MaxLength(50)]
        public string LastName { get; set; }

        [Required(ErrorMessage = "First name is required.")]
        [Column("FirstName")]
        [Display(Name = "First Name")]
        [MaxLength(50)]
        public string FirstMidName { get; set; }

        public string FullName 
        {
            get
            {
                return LastName + ", " + FirstMidName;
            }
        }
    }
}

在 Instructor.cs 文件中,將 Instructor 從 Person 派生出來,刪除 key 和 name 字段。代碼如下所示。

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

namespace ContosoUniversity.Models
{
    public class Instructor : Person
    {
        [DisplayFormat(DataFormatString = "{0:d}", ApplyFormatInEditMode = true)]
        [Required(ErrorMessage = "Hire date is required.")]
        [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;

namespace ContosoUniversity.Models
{
    public class Student : Person
    {
        [Required(ErrorMessage = "Enrollment date is required.")]
        [DisplayFormat(DataFormatString = "{0:d}", ApplyFormatInEditMode = true)]
        [Display(Name = "Enrollment Date")]
        public DateTime? EnrollmentDate { get; set; }

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

8-3  在模型中增加 Person 實體類型


在 SchoolContext.cs 文件中,對 Person 實體類型增加一個 DbSet 類型的屬性。

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

這就是在 EF 中配置 TPH 繼承所有需要完成的工作。如你所見,當數據庫重建的時候,EF 會自動創建 Person 表。

8-4  修改 InstructorID 和 StudentID 為 PersonID


在 SchoolContext.cs 文件中,在教師對課程的映射語句中,將 MapRightKey(“InstructorID”) 修改為 MapRightKey(“PersonID”)。

modelBuilder.Entity<Course>()
    .HasMany(c => c.Instructors).WithMany(i => i.Courses)
    .Map(t => t.MapLeftKey("CourseID")
    .MapRightKey("PersonID")
    .ToTable("CourseInstructor"));

這個修改不是必須的,它僅僅修改了在表多對多連接中 InstructorID 的名字。如果你保留名為仍然為 InstructorID,程序還是可以正常工作的。
下一步,進行一次全局的替換 ( 項目中所有的文件 ),將 InstructorID 修改為 PersonID,StudentID 修改為 PersonID。注意區分大小寫。( 這里演示了使用類名加上 ID 作為主鍵的一個缺點。如果你沒有使用類名加上 ID 作為主鍵,這里就不需要重新命名 )

8-5  在初始化器中修改主鍵的值

在 SchoolInitializer.cs 中,代碼假設對於 Student 和 Instructor 實體的主鍵數字是分離的。這對於 Student 實體來說仍然正確 ( 他們仍然從 1 到 8 ),但是對於 Instructor 實體來說,將不再從 1-5 而是修改為 9-13,因為在初始化類中的代碼將教師增加在同一張表的學生之后。使用下面的代碼修改 Department 和 OfficeAssignment 實體中的代碼,以便使用新的教師 ID。

var departments = new List<Department>
{
    new Department { Name = "English",     Budget = 350000, StartDate = DateTime.Parse("2007-09-01"), PersonID = 9 },
    new Department { Name = "Mathematics", Budget = 100000, StartDate = DateTime.Parse("2007-09-01"), PersonID = 10 },
    new Department { Name = "Engineering", Budget = 350000, StartDate = DateTime.Parse("2007-09-01"), PersonID = 11 },
    new Department { Name = "Economics",   Budget = 100000, StartDate = DateTime.Parse("2007-09-01"), PersonID = 12 }
};
var officeAssignments = new List<OfficeAssignment>
{
    new OfficeAssignment { PersonID = 9, Location = "Smith 17" },
    new OfficeAssignment { PersonID = 10, Location = "Gowan 27" },
    new OfficeAssignment { PersonID = 11, Location = "Thompson 304" },
};

8-6  將 OfficeAssignment 調整為延遲加載

當前版本的 EF 對導航屬性使用了 TPH繼承模式后的派生類,在一對一,或者一對零的關系上不支持預先加載模式。這對於 Instructor 實體上的 OfficeAssignment 屬性是個問題。解決這個問題,需要刪除在這個屬性上使用的預先加載處理。
在 InstructorController.cs 文件中,刪除三次出現的如下代碼。

 .Include(i => i.OfficeAssignment)

8-7  測試

運行程序,在各個頁面上檢查一下,所有的工作如以前一樣。
在解決方案管理器上,雙擊 School.sdf 數據庫,在服務器資源管理器中打開,展開 School.sdf,然后選擇 Tables,你會看到 Student 和 Instructor 表已經被 Person 表替換掉了。展開 Person 表,你會看到其中擁有原來 Student 和 Instructor 中所有的列,加上 discriminator 列。

下圖展示了新的 School 數據庫的結構。

對於 Person ,Student 和 Instructor 類,通過 TPH 實現了繼承。對於更多的關於其他繼承結構,可以查看 Morteza Manavi 的博客 Inheritance Mapping Strategies。在下一次的教程中,將會學習實現倉儲和單元模式的一些途徑。


免責聲明!

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



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