1. 程式人生 > >用ASP.NET Core MVC 和 EF Core 構建Web應用 (九)

用ASP.NET Core MVC 和 EF Core 構建Web應用 (九)

fix pro school time lap namespace 繼承映射 數據庫表 eas

在上一節中,已經處理了並發異常。 本節將演示如何在數據模型中實現繼承。

在面向對象的編程中,可以使用繼承以便於重用代碼。 在本教程中,將更改 InstructorStudent 類,以便從 Person 基類中派生,該基類包含教師和學生所共有的屬性(如 LastName)。 不會添加或更改任何網頁,但會更改部分代碼,並將在數據庫中自動反映這些更改。

將繼承映射到數據庫表的選項

學校數據模型中的 InstructorStudent 類具有多個相同的屬性:

技術分享圖片

假設想要消除由 InstructorStudent 實體共享的屬性的冗余代碼。 或者想要寫入可以格式化姓名的服務,而無需關註該姓名來自教師還是學生。 可以創建只包含這些共享屬性的 Person

基類,然後使 InstructorStudent 類繼承該基類,如下圖所示:

技術分享圖片

有多種方法可以在數據庫中表示此繼承結構。 可以創建一個 Person 表,將學生和教師的相關信息包含在一個表中。 某些列可能僅適用於教師 (HireDate),某些列僅適用於學生 (EnrollmentDate),某些列同時適用於兩者(LastName、FirstName)。 通常情況下,將有一個鑒別器列來指示每行所代表的類型。 例如,鑒別器列可能包含“Instructor”來指示教師,包含“Student”來指示學生。

技術分享圖片

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

另一種方法是使數據庫看起來更像繼承結構。 例如,可以僅將姓名字段包含到 Person 表中,在單獨的 Instructor 和 Student 表中包含日期字段。

技術分享圖片

為每個實體類創建數據庫表的模式稱為每個類型一張表 (TPT) 繼承。

另一種方法是將所有非抽象類型映射到單獨的表。 類的所有屬性(包括繼承的屬性)映射到相應表的列。 此模式稱為每個具體類一張表 (TPC) 繼承。 如果為前面所示的 Person、Student 和 Instructor 類實現了 TPC 繼承,那麽在實現繼承之後,Student 和 Instructor 表看起來將與以前沒什麽不同。

TPC 和 TPH 繼承模式的性能通常比 TPT 繼承模式好,因為 TPT 模式會導致復雜的聯接查詢。

本教程將演示如何實現 TPH 繼承。 TPH 是 Entity Framework Core 唯一支持的繼承模式。需要執行的操作是創建 Person 類、將 InstructorStudent 類更改為從 Person 派生、將新的類添加到 DbContext,以及創建遷移。

提示在進行以下更改之前,請考慮保存項目的副本。 如果遇到問題並需要重新開始,可以更輕松地從已保存的項目開始,而不用反向操作本教程中的步驟或者返回到整個系列的開始。

創建 Person 類

在 Models 文件夾中,創建 Person.cs 並使用以下代碼替換模板代碼:

技術分享圖片
 1 using System.ComponentModel.DataAnnotations;
 2 using System.ComponentModel.DataAnnotations.Schema;
 3 
 4 namespace ContosoUniversity.Models
 5 {
 6     public abstract class Person
 7     {
 8         public int ID { get; set; }
 9 
10         [Required]
11         [StringLength(50)]
12         [Display(Name = "Last Name")]
13         public string LastName { get; set; }
14         [Required]
15         [StringLength(50, ErrorMessage = "First name cannot be longer than 50 characters.")]
16         [Column("FirstName")]
17         [Display(Name = "First Name")]
18         public string FirstMidName { get; set; }
19 
20         [Display(Name = "Full Name")]
21         public string FullName
22         {
23             get
24             {
25                 return LastName + ", " + FirstMidName;
26             }
27         }
28     }
29 }
View Code

使 Student 和 Instructor 類從 Person 繼承

在 Instructor.cs 中,從 Person 類派生 Instructor 類並刪除鍵和姓名字段。 代碼將如下所示:

技術分享圖片
 1 using System;
 2 using System.Collections.Generic;
 3 using System.ComponentModel.DataAnnotations;
 4 using System.ComponentModel.DataAnnotations.Schema;
 5 
 6 namespace ContosoUniversity.Models
 7 {
 8     public class Instructor : Person
 9     {
10         [DataType(DataType.Date)]
11         [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
12         [Display(Name = "Hire Date")]
13         public DateTime HireDate { get; set; }
14 
15         public ICollection<CourseAssignment> CourseAssignments { get; set; }
16         public OfficeAssignment OfficeAssignment { get; set; }
17     }
18 }
View Code

在 Student.cs 中做出相同更改。

技術分享圖片
 1 using System;
 2 using System.Collections.Generic;
 3 using System.ComponentModel.DataAnnotations;
 4 using System.ComponentModel.DataAnnotations.Schema;
 5 
 6 namespace ContosoUniversity.Models
 7 {
 8     public class Student : Person
 9     {
10         [DataType(DataType.Date)]
11         [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
12         [Display(Name = "Enrollment Date")]
13         public DateTime EnrollmentDate { get; set; }
14 
15 
16         public ICollection<Enrollment> Enrollments { get; set; }
17     }
18 }
View Code

將 Person 實體類型添加到數據模型

技術分享圖片
 1 using ContosoUniversity.Models;
 2 using Microsoft.EntityFrameworkCore;
 3 
 4 namespace ContosoUniversity.Data
 5 {
 6     public class SchoolContext : DbContext
 7     {
 8         public SchoolContext(DbContextOptions<SchoolContext> options) : base(options)
 9         {
10         }
11 
12         public DbSet<Course> Courses { get; set; }
13         public DbSet<Enrollment> Enrollments { get; set; }
14         public DbSet<Student> Students { get; set; }
15         public DbSet<Department> Departments { get; set; }
16         public DbSet<Instructor> Instructors { get; set; }
17         public DbSet<OfficeAssignment> OfficeAssignments { get; set; }
18         public DbSet<CourseAssignment> CourseAssignments { get; set; }
19         public DbSet<Person> People { get; set; }
20 
21         protected override void OnModelCreating(ModelBuilder modelBuilder)
22         {
23             modelBuilder.Entity<Course>().ToTable("Course");
24             modelBuilder.Entity<Enrollment>().ToTable("Enrollment");
25             modelBuilder.Entity<Student>().ToTable("Student");
26             modelBuilder.Entity<Department>().ToTable("Department");
27             modelBuilder.Entity<Instructor>().ToTable("Instructor");
28             modelBuilder.Entity<OfficeAssignment>().ToTable("OfficeAssignment");
29             modelBuilder.Entity<CourseAssignment>().ToTable("CourseAssignment");
30             modelBuilder.Entity<Person>().ToTable("Person");
31 
32             modelBuilder.Entity<CourseAssignment>()
33                 .HasKey(c => new { c.CourseID, c.InstructorID });
34         }
35     }
36 }
View Code

以上是 Entity Framework 配置每個層次結構一張表繼承所需的全部操作。 正如將看到的,更新數據庫時,將有一個 Person 表來代替 Student 和 Instructor 表。

創建和自定義遷移代碼

保存更改並生成項目。 隨後在項目文件夾中打開命令窗口並輸入以下命令:

dotnet ef migrations add Inheritance

暫不運行 database update 命令。 該命令將導致數據丟失,因為它將刪除 Instructor 表並將 Student 表重命名為 Person。 需要提供自定義代碼來保留現有數據。

打開 Migrations/<timestamp>_Inheritance.cs 並使用以下代碼替換 Up 方法:

技術分享圖片
 1 protected override void Up(MigrationBuilder migrationBuilder)
 2 {
 3     migrationBuilder.DropForeignKey(
 4         name: "FK_Enrollment_Student_StudentID",
 5         table: "Enrollment");
 6 
 7     migrationBuilder.DropIndex(name: "IX_Enrollment_StudentID", table: "Enrollment");
 8 
 9     migrationBuilder.RenameTable(name: "Instructor", newName: "Person");
10     migrationBuilder.AddColumn<DateTime>(name: "EnrollmentDate", table: "Person", nullable: true);
11     migrationBuilder.AddColumn<string>(name: "Discriminator", table: "Person", nullable: false, maxLength: 128, defaultValue: "Instructor");
12     migrationBuilder.AlterColumn<DateTime>(name: "HireDate", table: "Person", nullable: true);
13     migrationBuilder.AddColumn<int>(name: "OldId", table: "Person", nullable: true);
14 
15     // Copy existing Student data into new Person table.
16     migrationBuilder.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");
17     // Fix up existing relationships to match new PK‘s.
18     migrationBuilder.Sql("UPDATE dbo.Enrollment SET StudentId = (SELECT ID FROM dbo.Person WHERE OldId = Enrollment.StudentId AND Discriminator = ‘Student‘)");
19 
20     // Remove temporary key
21     migrationBuilder.DropColumn(name: "OldID", table: "Person");
22 
23     migrationBuilder.DropTable(
24         name: "Student");
25 
26     migrationBuilder.CreateIndex(
27          name: "IX_Enrollment_StudentID",
28          table: "Enrollment",
29          column: "StudentID");
30 
31     migrationBuilder.AddForeignKey(
32         name: "FK_Enrollment_Person_StudentID",
33         table: "Enrollment",
34         column: "StudentID",
35         principalTable: "Person",
36         principalColumn: "ID",
37         onDelete: ReferentialAction.Cascade);
38 }
View Code

此代碼負責以下數據庫更新任務:

  • 刪除指向 Student 表的外鍵約束和索引。

  • 將 Instructor 表重命名為 Person,根據需要做出更改以存儲學生數據:

  • 為學生添加可為 NULL 的 EnrollmentDate。

  • 添加鑒別器列來指示行代表學生還是教師。

  • HireDate 可為 NULL,因為學生行不會包含聘用日期。

  • 添加臨時字段,用於更新指向學生的外鍵。 將學生復制到 Person 表時,將獲取新的主鍵值。

  • 將數據從 Student 表復制到 Person 表。 這將使學生獲取分配的新主鍵值。

  • 修復指向學生的外鍵值。

  • 重新創建外鍵約束和索引,現在將它們指向 Person 表。

(如果已使用 GUID 而不是整數作為主鍵類型,那麽將不需要更改學生主鍵值,並且可能已省略其中多個步驟。)

運行 database update 命令:

dotnet ef database update

(在生產系統中,可以對 Down 方法進行相應更改,以防必須使用該方法返回到以前的數據庫版本。

備註在包含現有數據的數據庫中更改架構時,可能會發生其他錯誤。 如果出現無法解決的遷移錯誤,可以在連接字符串中更改數據庫名或者刪除數據庫。 若是新數據庫,則沒有要遷移的數據,因此在完成更新數據庫命令時很可能不會出錯。 若要刪除數據庫,請使用 SSOX 或運行 database drop CLI 命令。

使用已實現的繼承進行測試

運行應用並嘗試各種頁面。 一切都和以前一樣。

在“SQL Server 對象資源管理器” 中,展開“數據連接/SchoolContext”和“表”,將看到 Student 和 Instructor 表已替換為 Person 表。 打開 Person 表設計器,將看到它包含在 Student 和 Instructor 表中使用的所有列。

技術分享圖片

右鍵單擊 Person 表,然後單擊“顯示表數據”以查看鑒別器列。

技術分享圖片

總結

你已經為 PersonStudentInstructor 類實現了每個層次結構一張表繼承。

*****************************
*** Keep learning and growing. ***
*****************************

用ASP.NET Core MVC 和 EF Core 構建Web應用 (九)