Nguon: Microsoft Learn · .NET 8.0

Phần 5: Razor Pages với EF Core trong ASP.NET Core - Tạo Data Model Phức Tạp

Nguồn: Part 5: Razor Pages - EF Core Complex Data Model

Hướng dẫn toàn diện này minh họa cách tạo data model (mô hình dữ liệu) phức tạp cho ứng dụng web Contoso University sử dụng Entity Framework Core và Razor Pages trong ASP.NET Core.

Tổng quan

Hướng dẫn này xây dựng dựa trên các hướng dẫn trước đã giới thiệu một data model cơ bản gồm ba entity (thực thể). Phần này mở rộng model bằng cách:

Các Entity chính

Entity Student

Entity Student bao gồm các thuộc tính với data annotation (chú thích dữ liệu) để validation và định dạng:

csharp
public class Student
{
    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; }
    [DataType(DataType.Date)]
    [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
    [Display(Name = "Enrollment Date")]
    public DateTime EnrollmentDate { get; set; }
    [Display(Name = "Full Name")]
    public string FullName
    {
        get { return LastName + ", " + FirstMidName; }
    }
    public ICollection<Enrollment> Enrollments { get; set; }
}

Giải thích các Attribute (thuộc tính) chính:

Entity Instructor

csharp
public class Instructor
{
    public int ID { get; set; }
    [Required]
    [Display(Name = "Last Name")]
    [StringLength(50)]
    public string LastName { get; set; }
    [Required]
    [Column("FirstName")]
    [Display(Name = "First Name")]
    [StringLength(50)]
    public string FirstMidName { get; set; }
    [DataType(DataType.Date)]
    [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
    [Display(Name = "Hire Date")]
    public DateTime HireDate { get; set; }
    [Display(Name = "Full Name")]
    public string FullName
    {
        get { return LastName + ", " + FirstMidName; }
    }
    public ICollection<Course> Courses { get; set; }
    public OfficeAssignment OfficeAssignment { get; set; }
}

Navigation properties (thuộc tính điều hướng):

Entity OfficeAssignment

csharp
public class OfficeAssignment
{
    [Key]
    public int InstructorID { get; set; }
    [StringLength(50)]
    [Display(Name = "Office Location")]
    public string Location { get; set; }
    public Instructor Instructor { get; set; }
}

Đặc điểm chính:

Entity Course

csharp
public class Course
{
    [DatabaseGenerated(DatabaseGeneratedOption.None)]
    [Display(Name = "Number")]
    public int CourseID { get; set; }
    [StringLength(50, MinimumLength = 3)]
    public string Title { get; set; }
    [Range(0, 5)]
    public int Credits { get; set; }
    public int DepartmentID { get; set; }
    public Department Department { get; set; }
    public ICollection<Enrollment> Enrollments { get; set; }
    public ICollection<Instructor> Instructors { get; set; }
}

Các Attribute đáng chú ý:

Entity Department

csharp
public class Department
{
    public int DepartmentID { get; set; }
    [StringLength(50, MinimumLength = 3)]
    public string Name { get; set; }
    [DataType(DataType.Currency)]
    [Column(TypeName = "money")]
    public decimal Budget { get; set; }
    [DataType(DataType.Date)]
    [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
    [Display(Name = "Start Date")]
    public DateTime StartDate { get; set; }
    public int? InstructorID { get; set; }
    public Instructor Administrator { get; set; }
    public ICollection<Course> Courses { get; set; }
}

Attribute chính:

Entity Enrollment

csharp
public enum Grade
{
    A, B, C, D, F
}

public class Enrollment
{
    public int EnrollmentID { get; set; }
    public int CourseID { get; set; }
    public int StudentID { get; set; }
    [DisplayFormat(NullDisplayText = "No grade")]
    public Grade? Grade { get; set; }
    public Course Course { get; set; }
    public Student Student { get; set; }
}

Các quan hệ Many-to-Many

Hướng dẫn đề cập hai loại quan hệ many-to-many:

1. Student-to-Course (có payload - dữ liệu bổ sung)

Entity Enrollment đóng vai trò là join table (bảng liên kết) với dữ liệu bổ sung:

2. Instructor-to-Course (pure join table - bảng liên kết thuần túy)

Sử dụng join table để kết nối instructors với courses mà không có dữ liệu bổ sung.

Cấu hình Database Context

csharp
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; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Course>().ToTable(nameof(Course))
            .HasMany(c => c.Instructors)
            .WithMany(i => i.Courses);
        modelBuilder.Entity<Student>().ToTable(nameof(Student));
        modelBuilder.Entity<Instructor>().ToTable(nameof(Instructor));
    }
}

Data Annotations vs. Fluent API

Hướng dẫn giải thích hai cách tiếp cận để cấu hình model:

Data Annotations: Sử dụng trực tiếp trên các thuộc tính

Fluent API: Cấu hình trong OnModelCreating

Ví dụ Fluent API cho composite key:

csharp
modelBuilder.Entity<CourseAssignment>()
    .HasKey(c => new { c.CourseID, c.InstructorID });

Migrations

Hướng dẫn minh họa:

  1. Tạo migration để áp dụng thay đổi schema:
powershell
Add-Migration ColumnFirstName
Update-Database
  1. Xử lý database hiện có với migrations
  2. Cách thay thế: drop và recreate database

Seeding Database

Phương thức DbInitializer.Initialize cung cấp dữ liệu mẫu:

Các điểm quan trọng

  1. Data Annotations cung cấp cách khai báo để chỉ định các quy tắc validation và định dạng
  2. Foreign Keys có thể tường minh hoặc ngầm định; thuộc tính FK tường minh cải thiện hiệu quả cập nhật
  3. Navigation Properties cho phép quan hệ giữa các entity
  4. Join Tables có thể có payload (dữ liệu bổ sung) như Enrollment.Grade
  5. Fluent API xử lý các cấu hình phức tạp mà attribute không thể biểu đạt
  6. Migrations theo dõi thay đổi schema và có thể được áp dụng hoặc hoàn tác
  7. Cascade Delete phải được cấu hình cẩn thận để tránh các phụ thuộc vòng tròn

Data model hoàn chỉnh đại diện cho database đại học thực tế với sinh viên, giảng viên, khóa học, khoa, và quản lý đăng ký học.