Hướng dẫn: Triển khai Inheritance (kế thừa) - ASP.NET MVC với EF Core
Trong hướng dẫn trước, bạn đã xử lý các ngoại lệ concurrency. Hướng dẫn này trình bày cách triển khai inheritance (kế thừa) trong mô hình dữ liệu.
Trong lập trình hướng đối tượng (object-oriented programming), bạn có thể sử dụng inheritance để tạo điều kiện tái sử dụng code. Trong hướng dẫn này, bạn sẽ thay đổi các lớp Instructor và Student để chúng kế thừa từ lớp cơ sở Person chứa các thuộc tính như LastName (họ) chung cho cả instructor và student. Bạn sẽ không thêm hoặc thay đổi bất kỳ trang web nào, nhưng sẽ thay đổi một số code và những thay đổi đó sẽ được phản ánh tự động trong cơ sở dữ liệu.
Trong hướng dẫn này, bạn sẽ:
- Ánh xạ inheritance vào cơ sở dữ liệu
- Tạo lớp Person
- Cập nhật Instructor và Student
- Thêm Person vào model
- Tạo và cập nhật migrations
- Kiểm tra việc triển khai
Điều kiện tiên quyết
Ánh xạ Inheritance vào cơ sở dữ liệu
Các lớp Instructor và Student trong mô hình dữ liệu School có nhiều thuộc tính giống hệt nhau:
Giả sử bạn muốn loại bỏ code trùng lặp cho các thuộc tính được chia sẻ bởi các thực thể Instructor và Student. Hoặc bạn muốn viết một service (dịch vụ) có thể định dạng tên mà không cần quan tâm đến tên đến từ instructor hay student. Bạn có thể tạo một lớp cơ sở Person chỉ chứa những thuộc tính chia sẻ đó, sau đó làm cho các lớp Instructor và Student kế thừa từ lớp cơ sở đó.
Có nhiều cách để biểu diễn cấu trúc inheritance này trong cơ sở dữ liệu:
- Table-per-hierarchy (TPH): Một bảng
Personbao gồm thông tin về cả student và instructor trong một bảng duy nhất. Có một cột discriminator (phân biệt) để chỉ ra loại mỗi hàng. - Table-per-type (TPT): Chỉ có các trường tên trong bảng
Personvà có bảngInstructorvàStudentriêng biệt với các trường ngày tháng. - Table-per-Concrete Class (TPC): Ánh xạ tất cả các kiểu không trừu tượng vào các bảng riêng lẻ. Tất cả thuộc tính của một lớp, kể cả các thuộc tính được kế thừa, đều ánh xạ vào các cột của bảng tương ứng.
Hướng dẫn này minh họa cách triển khai TPH inheritance. TPH là pattern (mẫu) inheritance duy nhất mà Entity Framework Core hỗ trợ.
Tạo lớp Person
Trong thư mục Models, tạo Person.cs và thay thế code template (mẫu) bằng code sau:
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;
}
}
}
}Cập nhật Instructor và Student
Trong Instructor.cs, cho lớp Instructor kế thừa từ lớp Person và xóa các trường key và tên. Code sẽ trông như sau:
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 ICollection<CourseAssignment> CourseAssignments { get; set; }
public OfficeAssignment OfficeAssignment { get; set; }
}
}Thực hiện tương tự trong Student.cs:
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 ICollection<Enrollment> Enrollments { get; set; }
}
}Thêm Person vào Model
Thêm kiểu thực thể Person vào SchoolContext.cs:
using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;
namespace ContosoUniversity.Data
{
public class SchoolContext : DbContext
{
public SchoolContext(DbContextOptions<SchoolContext> options) : base(options)
{
}
public DbSet<Course> Courses { get; set; }
public DbSet<Enrollment> Enrollments { get; set; }
public DbSet<Student> Students { get; set; }
public DbSet<Department> Departments { get; set; }
public DbSet<Instructor> Instructors { get; set; }
public DbSet<OfficeAssignment> OfficeAssignments { get; set; }
public DbSet<CourseAssignment> CourseAssignments { get; set; }
public DbSet<Person> People { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Course>().ToTable("Course");
modelBuilder.Entity<Enrollment>().ToTable("Enrollment");
modelBuilder.Entity<Student>().ToTable("Student");
modelBuilder.Entity<Department>().ToTable("Department");
modelBuilder.Entity<Instructor>().ToTable("Instructor");
modelBuilder.Entity<OfficeAssignment>().ToTable("OfficeAssignment");
modelBuilder.Entity<CourseAssignment>().ToTable("CourseAssignment");
modelBuilder.Entity<Person>().ToTable("Person");
modelBuilder.Entity<CourseAssignment>()
.HasKey(c => new { c.CourseID, c.InstructorID });
}
}
}Đây là tất cả những gì Entity Framework cần để cấu hình TPH inheritance. Khi cơ sở dữ liệu được cập nhật, nó sẽ có bảng Person thay thế cho bảng Student và Instructor.
Tạo và cập nhật Migrations
Lưu các thay đổi và build (xây dựng) dự án. Sau đó mở cửa sổ lệnh trong thư mục dự án và nhập lệnh sau:
dotnet ef migrations add Inheritance
Đừng chạy lệnh database update ngay. Lệnh đó sẽ dẫn đến mất dữ liệu vì nó sẽ xóa bảng Instructor và đổi tên bảng Student thành Person. Bạn cần cung cấp code tùy chỉnh để bảo tồn dữ liệu hiện có.
Mở Migrations/<timestamp>_Inheritance.cs và thay thế phương thức Up bằng code sau:
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Enrollment_Student_StudentID",
table: "Enrollment");
migrationBuilder.DropIndex(name: "IX_Enrollment_StudentID", table: "Enrollment");
migrationBuilder.RenameTable(name: "Instructor", newName: "Person");
migrationBuilder.AddColumn<DateTime>(name: "EnrollmentDate", table: "Person", nullable: true);
migrationBuilder.AddColumn<string>(name: "Discriminator", table: "Person", nullable: false, maxLength: 128, defaultValue: "Instructor");
migrationBuilder.AlterColumn<DateTime>(name: "HireDate", table: "Person", nullable: true);
migrationBuilder.AddColumn<int>(name: "OldId", table: "Person", nullable: true);
// Copy existing Student data into new Person table.
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");
// Fix up existing relationships to match new PK's.
migrationBuilder.Sql("UPDATE dbo.Enrollment SET StudentId = (SELECT ID FROM dbo.Person WHERE OldId = Enrollment.StudentId AND Discriminator = 'Student')");
// Remove temporary key
migrationBuilder.DropColumn(name: "OldID", table: "Person");
migrationBuilder.DropTable(
name: "Student");
migrationBuilder.CreateIndex(
name: "IX_Enrollment_StudentID",
table: "Enrollment",
column: "StudentID");
migrationBuilder.AddForeignKey(
name: "FK_Enrollment_Person_StudentID",
table: "Enrollment",
column: "StudentID",
principalTable: "Person",
principalColumn: "ID",
onDelete: ReferentialAction.Cascade);
}Code này xử lý các tác vụ cập nhật cơ sở dữ liệu sau:
- Xóa các ràng buộc khóa ngoại (foreign key constraints) và index trỏ đến bảng Student.
- Đổi tên bảng Instructor thành Person và thực hiện các thay đổi cần thiết để lưu trữ dữ liệu Student.
- Thêm cột EnrollmentDate cho phép null cho student.
- Thêm cột Discriminator để chỉ ra liệu một hàng là dành cho student hay instructor.
- Làm cho HireDate cho phép null vì các hàng student sẽ không có ngày thuê.
- Thêm trường tạm thời sẽ được sử dụng để cập nhật khóa ngoại trỏ đến student.
- Sao chép dữ liệu từ bảng Student vào bảng Person.
- Sửa các giá trị khóa ngoại trỏ đến student.
- Tạo lại các ràng buộc khóa ngoại và index, giờ trỏ đến bảng Person.
Chạy lệnh database update:
dotnet ef database update
Kiểm tra việc triển khai
Chạy ứng dụng và thử các trang khác nhau. Mọi thứ hoạt động giống như trước.
Trong SQL Server Object Explorer, mở rộng Data Connections/SchoolContext và sau đó Tables, bạn sẽ thấy rằng các bảng Student và Instructor đã được thay thế bằng bảng Person. Mở trình thiết kế bảng Person và bạn sẽ thấy nó có tất cả các cột từng có trong bảng Student và Instructor.
Nhấp chuột phải vào bảng Person, sau đó nhấp Show Table Data để xem cột discriminator.