Nguon: Microsoft Learn · .NET 8.0

Hướng dẫn: Tạo mô hình dữ liệu phức tạp - ASP.NET MVC với EF Core

Nguồn: Tutorial: Create a complex data model - ASP.NET MVC with EF Core

Trong các hướng dẫn trước, bạn đã làm việc với một mô hình dữ liệu đơn giản gồm ba entity. Trong hướng dẫn này, bạn sẽ thêm nhiều entity và mối quan hệ hơn, và bạn sẽ tùy chỉnh mô hình dữ liệu bằng cách chỉ định các quy tắc định dạng, validation (xác thực) và mapping (ánh xạ) cơ sở dữ liệu.

Khi hoàn thành, các lớp entity sẽ tạo thành mô hình dữ liệu hoàn chỉnh được hiển thị trong hình minh họa sau:

Trong hướng dẫn này, bạn:

Điều kiện tiên quyết

Tùy chỉnh mô hình dữ liệu

Trong phần này, bạn sẽ thấy cách tùy chỉnh mô hình dữ liệu bằng cách sử dụng các attribute (thuộc tính) chỉ định các quy tắc định dạng, validation và mapping cơ sở dữ liệu.

Attribute DataType

Đối với ngày đăng ký sinh viên, tất cả các trang web hiện tại hiển thị cả thời gian cùng với ngày, mặc dù bạn chỉ quan tâm đến ngày cho trường này. Bằng cách sử dụng các attribute data annotation (chú thích dữ liệu), bạn có thể thực hiện một thay đổi mã sẽ sửa định dạng hiển thị trong mọi view hiển thị dữ liệu. Để xem ví dụ về cách thực hiện, bạn sẽ thêm một attribute vào thuộc tính EnrollmentDate trong class Student.

Trong Models/Student.cs, thêm câu lệnh using cho namespace System.ComponentModel.DataAnnotations và thêm các attribute DataTypeDisplayFormat vào thuộc tính EnrollmentDate, như được hiển thị trong ví dụ sau:

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

namespace ContosoUniversity.Models
{
    public class Student
    {
        public int ID { get; set; }
        public string LastName { get; set; }
        public string FirstMidName { get; set; }
        [DataType(DataType.Date)]
        [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
        public DateTime EnrollmentDate { get; set; }

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

Attribute DataType được sử dụng để chỉ định kiểu dữ liệu cụ thể hơn kiểu intrinsic (cơ bản) của cơ sở dữ liệu. Trong trường hợp này, chúng ta chỉ muốn theo dõi ngày, không phải ngày và giờ. Enum DataType cung cấp nhiều kiểu dữ liệu, như Date, Time, PhoneNumber, Currency, EmailAddress và nhiều kiểu khác. Attribute DataType cũng có thể cho phép ứng dụng tự động cung cấp các tính năng cụ thể cho kiểu. Attribute DataType phát ra các attribute HTML 5 data- mà các trình duyệt HTML 5 có thể hiểu. Các attribute DataType không cung cấp bất kỳ validation nào.

DataType.Date không chỉ định định dạng của ngày được hiển thị. Theo mặc định, trường dữ liệu được hiển thị theo các định dạng mặc định dựa trên CultureInfo của máy chủ.

Attribute DisplayFormat được sử dụng để chỉ định rõ ràng định dạng ngày:

csharp
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]

Cài đặt ApplyFormatInEditMode chỉ định rằng định dạng cũng nên được áp dụng khi giá trị được hiển thị trong ô text để chỉnh sửa.

Chạy ứng dụng, đến trang Students Index và lưu ý rằng thời gian không còn được hiển thị cho ngày đăng ký.

Attribute StringLength

Bạn cũng có thể chỉ định các quy tắc validation dữ liệu và thông báo lỗi validation bằng cách sử dụng các attribute. Attribute StringLength đặt độ dài tối đa trong cơ sở dữ liệu và cung cấp validation phía client và phía server cho ASP.NET Core MVC.

Giả sử bạn muốn đảm bảo người dùng không nhập quá 50 ký tự cho tên. Để thêm giới hạn này, hãy thêm các attribute StringLength vào các thuộc tính LastNameFirstMidName, như được hiển thị trong ví dụ sau:

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

namespace ContosoUniversity.Models
{
    public class Student
    {
        public int ID { get; set; }
        [StringLength(50)]
        public string LastName { get; set; }
        [StringLength(50)]
        public string FirstMidName { get; set; }
        [DataType(DataType.Date)]
        [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
        public DateTime EnrollmentDate { get; set; }

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

Attribute StringLength sẽ không ngăn người dùng nhập khoảng trắng cho tên. Bạn có thể sử dụng attribute RegularExpression để áp dụng các hạn chế cho đầu vào.

Mô hình cơ sở dữ liệu bây giờ đã thay đổi theo cách yêu cầu thay đổi trong schema cơ sở dữ liệu. Bạn sẽ sử dụng migrations để cập nhật schema mà không mất bất kỳ dữ liệu nào bạn có thể đã thêm vào cơ sở dữ liệu bằng cách sử dụng UI ứng dụng.

Lưu các thay đổi của bạn và build dự án. Sau đó mở cửa sổ lệnh trong thư mục dự án và nhập các lệnh sau:

dotnetcli
dotnet ef migrations add MaxLengthOnNames
dotnetcli
dotnet ef database update

Attribute Column

Bạn cũng có thể sử dụng các attribute để kiểm soát cách các class và thuộc tính được ánh xạ đến cơ sở dữ liệu. Giả sử bạn đã sử dụng tên FirstMidName cho trường tên đầu vì trường đó cũng có thể chứa tên đệm. Nhưng bạn muốn cột cơ sở dữ liệu được đặt tên là FirstName, vì những người sẽ viết các truy vấn ad-hoc (tự phát) đối với cơ sở dữ liệu quen với tên đó. Để thực hiện ánh xạ này, bạn có thể sử dụng attribute Column.

Attribute Column chỉ định rằng khi cơ sở dữ liệu được tạo, cột của bảng Student ánh xạ đến thuộc tính FirstMidName sẽ được đặt tên là FirstName.

Trong file Student.cs, thêm câu lệnh using cho System.ComponentModel.DataAnnotations.Schema và thêm attribute tên cột vào thuộc tính FirstMidName:

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

namespace ContosoUniversity.Models
{
    public class Student
    {
        public int ID { get; set; }
        [StringLength(50)]
        public string LastName { get; set; }
        [StringLength(50)]
        [Column("FirstName")]
        public string FirstMidName { get; set; }
        [DataType(DataType.Date)]
        [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
        public DateTime EnrollmentDate { get; set; }

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

Lưu các thay đổi và build dự án. Sau đó mở cửa sổ lệnh trong thư mục dự án và nhập các lệnh sau để tạo migration mới:

dotnetcli
dotnet ef migrations add ColumnFirstName
dotnetcli
dotnet ef database update

Thay đổi entity Student

Trong Models/Student.cs, thay thế mã bạn đã thêm trước đó bằng mã sau:

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

namespace ContosoUniversity.Models
{
    public class Student
    {
        public int ID { get; set; }
        [Required]
        [StringLength(50)]
        [Display(Name = "Last Name")]
        public string LastName { get; set; }
        [Required]
        [StringLength(50)]
        [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; }
    }
}

Attribute Required

Attribute Required làm cho các thuộc tính tên trở thành các trường bắt buộc. Attribute Required không cần thiết cho các kiểu không nullable (không thể null) như kiểu value (DateTime, int, double, float, v.v.). Các kiểu không thể null được tự động coi là các trường bắt buộc.

Attribute Display

Attribute Display chỉ định rằng caption (chú thích) cho các ô text phải là "First Name", "Last Name", "Full Name" và "Enrollment Date" thay vì tên thuộc tính trong mỗi instance.

Thuộc tính tính toán FullName

FullName là một thuộc tính tính toán trả về một giá trị được tạo bằng cách nối hai thuộc tính khác. Do đó, nó chỉ có getter, và không có cột FullName nào được tạo trong cơ sở dữ liệu.

Tạo entity Instructor

Tạo Models/Instructor.cs, thay thế mã template bằng mã sau:

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

namespace ContosoUniversity.Models
{
    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<CourseAssignment> CourseAssignments { get; set; }
        public OfficeAssignment OfficeAssignment { get; set; }
    }
}

Lưu ý rằng một số thuộc tính giống nhau trong các entity Student và Instructor. Trong hướng dẫn Implementing Inheritance (Triển khai kế thừa) sau này trong series này, bạn sẽ refactor mã này để loại bỏ sự dư thừa.

Các thuộc tính navigation CourseAssignments và OfficeAssignment

Các thuộc tính CourseAssignmentsOfficeAssignment là các thuộc tính navigation.

Một instructor có thể giảng dạy bất kỳ số lượng khóa học nào, vì vậy CourseAssignments được định nghĩa là một collection:

csharp
public ICollection<CourseAssignment> CourseAssignments { get; set; }

Nếu một thuộc tính navigation có thể chứa nhiều entity, kiểu của nó phải là một danh sách có thể thêm, xóa và cập nhật các mục. Bạn có thể chỉ định ICollection<T> hoặc một kiểu như List<T> hoặc HashSet<T>. Nếu bạn chỉ định ICollection<T>, EF tạo collection HashSet<T> theo mặc định.

Quy tắc kinh doanh của Contoso University nêu rằng một instructor chỉ có thể có tối đa một văn phòng, vì vậy thuộc tính OfficeAssignment giữ một entity OfficeAssignment duy nhất (có thể là null nếu không có văn phòng được phân công).

csharp
public OfficeAssignment OfficeAssignment { get; set; }

Tạo entity OfficeAssignment

Tạo Models/OfficeAssignment.cs với mã sau:

csharp
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
    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; }
    }
}

Attribute Key

Có một mối quan hệ một-đến-không-hoặc-một giữa các entity InstructorOfficeAssignment. Một phân công văn phòng chỉ tồn tại liên quan đến instructor mà nó được phân công, và do đó primary key (khóa chính) của nó cũng là foreign key (khóa ngoại) của nó cho entity Instructor. Nhưng Entity Framework không thể tự động nhận dạng InstructorID là primary key của entity này vì tên của nó không tuân theo quy ước đặt tên ID hoặc classnameID. Do đó, attribute Key được sử dụng để nhận dạng nó là key:

csharp
[Key]
public int InstructorID { get; set; }

Thuộc tính navigation Instructor

Entity Instructor có thuộc tính navigation OfficeAssignment nullable (vì một instructor có thể không có phân công văn phòng), và entity OfficeAssignment có thuộc tính navigation Instructor không nullable (vì phân công văn phòng không thể tồn tại mà không có instructor -- InstructorID không nullable).

Sửa đổi entity Course

Trong Models/Course.cs, thay thế mã bạn đã thêm trước đó bằng mã sau:

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

namespace ContosoUniversity.Models
{
    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<CourseAssignment> CourseAssignments { get; set; }
    }
}

Entity course có thuộc tính foreign key DepartmentID trỏ đến entity Department liên quan và có thuộc tính navigation Department.

Entity Framework không yêu cầu bạn thêm thuộc tính foreign key vào mô hình dữ liệu khi bạn có thuộc tính navigation cho một entity liên quan. EF tự động tạo foreign key trong cơ sở dữ liệu bất cứ khi nào chúng cần và tạo shadow properties (thuộc tính bóng) cho chúng. Nhưng việc có foreign key trong mô hình dữ liệu có thể làm cho các cập nhật đơn giản hơn và hiệu quả hơn.

Attribute DatabaseGenerated

Attribute DatabaseGenerated với tham số None trên thuộc tính CourseID chỉ định rằng các giá trị primary key được cung cấp bởi người dùng thay vì được tạo bởi cơ sở dữ liệu:

csharp
[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Display(Name = "Number")]
public int CourseID { get; set; }

Theo mặc định, Entity Framework giả định rằng các giá trị primary key được tạo bởi cơ sở dữ liệu. Đó là điều bạn muốn trong hầu hết các trường hợp. Tuy nhiên, đối với các entity Course, bạn sẽ sử dụng số khóa học do người dùng chỉ định như series 1000 cho một khoa, series 2000 cho khoa khác, v.v.

Tạo entity Department

Tạo Models/Department.cs với mã sau:

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

namespace ContosoUniversity.Models
{
    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 Column

Trước đây, bạn đã sử dụng attribute Column để thay đổi ánh xạ tên cột. Trong mã cho entity Department, attribute Column được sử dụng để thay đổi ánh xạ kiểu dữ liệu SQL sao cho cột sẽ được định nghĩa sử dụng kiểu money của SQL Server trong cơ sở dữ liệu:

csharp
[Column(TypeName="money")]
public decimal Budget { get; set; }

Foreign key và thuộc tính navigation

Thuộc tính foreign key và navigation phản ánh các mối quan hệ sau:

Một khoa có thể hoặc không có quản trị viên, và quản trị viên luôn là một instructor. Do đó, thuộc tính InstructorID được bao gồm như là foreign key cho entity Instructor, và dấu chấm hỏi được thêm sau chỉ định kiểu int để đánh dấu thuộc tính là nullable. Thuộc tính navigation được đặt tên là Administrator nhưng giữ một entity Instructor:

csharp
public int? InstructorID { get; set; }
public Instructor Administrator { get; set; }

Một khoa có thể có nhiều khóa học, vì vậy có thuộc tính navigation Courses:

csharp
public ICollection<Course> Courses { get; set; }

Lưu ý: Theo quy ước, Entity Framework kích hoạt cascade delete (xóa theo tầng) cho các foreign key không nullable và cho các mối quan hệ nhiều-đến-nhiều. Điều này có thể dẫn đến các quy tắc cascade delete vòng tròn, sẽ gây ra ngoại lệ khi bạn cố thêm migration. Ví dụ, nếu bạn không định nghĩa thuộc tính Department.InstructorID là nullable, EF sẽ cấu hình quy tắc cascade delete để xóa khoa khi bạn xóa instructor, điều này không phải là điều bạn muốn xảy ra. Nếu quy tắc kinh doanh của bạn yêu cầu thuộc tính InstructorID không nullable, bạn sẽ phải sử dụng câu lệnh fluent API sau để tắt cascade delete trên mối quan hệ: ``csharp modelBuilder.Entity<Department>() .HasOne(d => d.Administrator) .WithMany() .OnDelete(DeleteBehavior.Restrict) ``

Sửa đổi entity Enrollment

Trong Models/Enrollment.cs, thay thế mã bạn đã thêm trước đó bằng mã sau:

csharp
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
    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; }
    }
}

Foreign key và thuộc tính navigation

Các thuộc tính foreign key và navigation phản ánh các mối quan hệ sau:

Một bản ghi enrollment dành cho một khóa học duy nhất, vì vậy có thuộc tính foreign key CourseID và thuộc tính navigation Course:

csharp
public int CourseID { get; set; }
public Course Course { get; set; }

Một bản ghi enrollment dành cho một sinh viên duy nhất, vì vậy có thuộc tính foreign key StudentID và thuộc tính navigation Student:

csharp
public int StudentID { get; set; }
public Student Student { get; set; }

Mối quan hệ nhiều-đến-nhiều

Có một mối quan hệ nhiều-đến-nhiều giữa các entity StudentCourse, và entity Enrollment hoạt động như một bảng join nhiều-đến-nhiều có payload (dữ liệu bổ sung) trong cơ sở dữ liệu. "Có payload" có nghĩa là bảng Enrollment chứa dữ liệu bổ sung ngoài các foreign key cho các bảng được join (trong trường hợp này, một primary key và thuộc tính Grade).

Mỗi dòng mối quan hệ có 1 ở một đầu và dấu hoa thị (\*) ở đầu kia, chỉ ra mối quan hệ một-đến-nhiều.

Nếu bảng Enrollment không bao gồm thông tin điểm, nó sẽ chỉ cần chứa hai foreign key CourseIDStudentID. Trong trường hợp đó, nó sẽ là một bảng join nhiều-đến-nhiều không có payload (hoặc bảng join thuần túy) trong cơ sở dữ liệu. Các entity InstructorCourse có loại mối quan hệ nhiều-đến-nhiều đó, và bước tiếp theo của bạn là tạo một lớp entity để hoạt động như bảng join không có payload.

EF Core hỗ trợ các bảng join ngầm định cho các mối quan hệ nhiều-đến-nhiều, nhưng hướng dẫn này chưa được cập nhật để sử dụng bảng join ngầm định. Xem Many-to-Many Relationships, phiên bản Razor Pages của hướng dẫn này đã được cập nhật.

Entity CourseAssignment

Tạo Models/CourseAssignment.cs với mã sau:

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

namespace ContosoUniversity.Models
{
    public class CourseAssignment
    {
        public int InstructorID { get; set; }
        public int CourseID { get; set; }
        public Instructor Instructor { get; set; }
        public Course Course { get; set; }
    }
}

Tên entity join

Một bảng join là bắt buộc trong cơ sở dữ liệu cho mối quan hệ nhiều-đến-nhiều Instructor-to-Courses, và nó phải được đại diện bởi một entity set. Thông thường, entity join được đặt tên là EntityName1EntityName2, trong trường hợp này sẽ là CourseInstructor. Tuy nhiên, chúng tôi khuyến nghị bạn chọn một tên mô tả mối quan hệ. Các mô hình dữ liệu bắt đầu đơn giản và phát triển, với các join không có payload thường có payload sau này. Nếu bạn bắt đầu với tên entity mô tả, bạn sẽ không phải thay đổi tên sau này. Lý tưởng nhất, entity join sẽ có tên tự nhiên riêng của nó trong domain kinh doanh. Ví dụ, Books và Customers có thể được liên kết thông qua Ratings. Cho mối quan hệ này, CourseAssignment là lựa chọn tốt hơn CourseInstructor.

Composite key (Khóa tổ hợp)

Vì các foreign key không nullable và cùng nhau xác định duy nhất mỗi hàng của bảng, không cần primary key riêng. Các thuộc tính InstructorIDCourseID nên hoạt động như một composite primary key. Cách duy nhất để xác định các composite primary key cho EF là sử dụng fluent API (API trôi chảy) (không thể thực hiện bằng cách sử dụng các attribute). Bạn sẽ thấy cách cấu hình composite primary key trong phần tiếp theo.

Cập nhật database context

Thêm mã highlight sau vào file Data/SchoolContext.cs:

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

        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<CourseAssignment>()
                .HasKey(c => new { c.CourseID, c.InstructorID });
        }
    }
}

Mã này thêm các entity mới và cấu hình composite primary key của entity CourseAssignment.

Giới thiệu về fluent API

Mã trong phương thức OnModelCreating của lớp DbContext sử dụng fluent API để cấu hình hành vi EF. API được gọi là "fluent" (trôi chảy) vì nó thường được sử dụng bằng cách xâu chuỗi một loạt các lệnh gọi phương thức thành một câu lệnh duy nhất, như trong ví dụ này:

csharp
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .Property(b => b.Url)
        .IsRequired();
}

Trong hướng dẫn này, bạn chỉ sử dụng fluent API cho việc mapping cơ sở dữ liệu mà bạn không thể thực hiện với các attribute. Tuy nhiên, bạn cũng có thể sử dụng fluent API để chỉ định hầu hết các quy tắc định dạng, validation và mapping mà bạn có thể thực hiện bằng cách sử dụng các attribute.

Nạp dữ liệu kiểm tra vào cơ sở dữ liệu

Thay thế mã trong file Data/DbInitializer.cs bằng mã sau để cung cấp seed data (dữ liệu khởi tạo) cho các entity mới bạn đã tạo:

csharp
using System;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using ContosoUniversity.Models;

namespace ContosoUniversity.Data
{
    public static class DbInitializer
    {
        public static void Initialize(SchoolContext context)
        {
            //context.Database.EnsureCreated();

            // Look for any students.
            if (context.Students.Any())
            {
                return;   // DB has been seeded
            }

            var students = new Student[]
            {
                new Student { FirstMidName = "Carson",   LastName = "Alexander",
                    EnrollmentDate = DateTime.Parse("2010-09-01") },
                new Student { FirstMidName = "Meredith", LastName = "Alonso",
                    EnrollmentDate = DateTime.Parse("2012-09-01") },
                new Student { FirstMidName = "Arturo",   LastName = "Anand",
                    EnrollmentDate = DateTime.Parse("2013-09-01") },
                new Student { FirstMidName = "Gytis",    LastName = "Barzdukas",
                    EnrollmentDate = DateTime.Parse("2012-09-01") },
                new Student { FirstMidName = "Yan",      LastName = "Li",
                    EnrollmentDate = DateTime.Parse("2012-09-01") },
                new Student { FirstMidName = "Peggy",    LastName = "Justice",
                    EnrollmentDate = DateTime.Parse("2011-09-01") },
                new Student { FirstMidName = "Laura",    LastName = "Norman",
                    EnrollmentDate = DateTime.Parse("2013-09-01") },
                new Student { FirstMidName = "Nino",     LastName = "Olivetto",
                    EnrollmentDate = DateTime.Parse("2005-09-01") }
            };

            foreach (Student s in students)
            {
                context.Students.Add(s);
            }
            context.SaveChanges();

            var instructors = new Instructor[]
            {
                new Instructor { FirstMidName = "Kim",     LastName = "Abercrombie",
                    HireDate = DateTime.Parse("1995-03-11") },
                new Instructor { FirstMidName = "Fadi",    LastName = "Fakhouri",
                    HireDate = DateTime.Parse("2002-07-06") },
                new Instructor { FirstMidName = "Roger",   LastName = "Harui",
                    HireDate = DateTime.Parse("1998-07-01") },
                new Instructor { FirstMidName = "Candace", LastName = "Kapoor",
                    HireDate = DateTime.Parse("2001-01-15") },
                new Instructor { FirstMidName = "Roger",   LastName = "Zheng",
                    HireDate = DateTime.Parse("2004-02-12") }
            };

            foreach (Instructor i in instructors)
            {
                context.Instructors.Add(i);
            }
            context.SaveChanges();

            var departments = new Department[]
            {
                new Department { Name = "English",     Budget = 350000,
                    StartDate = DateTime.Parse("2007-09-01"),
                    InstructorID  = instructors.Single( i => i.LastName == "Abercrombie").ID },
                new Department { Name = "Mathematics", Budget = 100000,
                    StartDate = DateTime.Parse("2007-09-01"),
                    InstructorID  = instructors.Single( i => i.LastName == "Fakhouri").ID },
                new Department { Name = "Engineering", Budget = 350000,
                    StartDate = DateTime.Parse("2007-09-01"),
                    InstructorID  = instructors.Single( i => i.LastName == "Harui").ID },
                new Department { Name = "Economics",   Budget = 100000,
                    StartDate = DateTime.Parse("2007-09-01"),
                    InstructorID  = instructors.Single( i => i.LastName == "Kapoor").ID }
            };

            foreach (Department d in departments)
            {
                context.Departments.Add(d);
            }
            context.SaveChanges();

            var courses = new Course[]
            {
                new Course {CourseID = 1050, Title = "Chemistry",      Credits = 3,
                    DepartmentID = departments.Single( s => s.Name == "Engineering").DepartmentID
                },
                new Course {CourseID = 4022, Title = "Microeconomics", Credits = 3,
                    DepartmentID = departments.Single( s => s.Name == "Economics").DepartmentID
                },
                new Course {CourseID = 4041, Title = "Macroeconomics", Credits = 3,
                    DepartmentID = departments.Single( s => s.Name == "Economics").DepartmentID
                },
                new Course {CourseID = 1045, Title = "Calculus",       Credits = 4,
                    DepartmentID = departments.Single( s => s.Name == "Mathematics").DepartmentID
                },
                new Course {CourseID = 3141, Title = "Trigonometry",   Credits = 4,
                    DepartmentID = departments.Single( s => s.Name == "Mathematics").DepartmentID
                },
                new Course {CourseID = 2021, Title = "Composition",    Credits = 3,
                    DepartmentID = departments.Single( s => s.Name == "English").DepartmentID
                },
                new Course {CourseID = 2042, Title = "Literature",     Credits = 4,
                    DepartmentID = departments.Single( s => s.Name == "English").DepartmentID
                },
            };

            foreach (Course c in courses)
            {
                context.Courses.Add(c);
            }
            context.SaveChanges();

            var officeAssignments = new OfficeAssignment[]
            {
                new OfficeAssignment {
                    InstructorID = instructors.Single( i => i.LastName == "Fakhouri").ID,
                    Location = "Smith 17" },
                new OfficeAssignment {
                    InstructorID = instructors.Single( i => i.LastName == "Harui").ID,
                    Location = "Gowan 27" },
                new OfficeAssignment {
                    InstructorID = instructors.Single( i => i.LastName == "Kapoor").ID,
                    Location = "Thompson 304" },
            };

            foreach (OfficeAssignment o in officeAssignments)
            {
                context.OfficeAssignments.Add(o);
            }
            context.SaveChanges();

            var courseInstructors = new CourseAssignment[]
            {
                new CourseAssignment {
                    CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID,
                    InstructorID = instructors.Single(i => i.LastName == "Kapoor").ID
                    },
                new CourseAssignment {
                    CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID,
                    InstructorID = instructors.Single(i => i.LastName == "Harui").ID
                    },
                new CourseAssignment {
                    CourseID = courses.Single(c => c.Title == "Microeconomics" ).CourseID,
                    InstructorID = instructors.Single(i => i.LastName == "Zheng").ID
                    },
                new CourseAssignment {
                    CourseID = courses.Single(c => c.Title == "Macroeconomics" ).CourseID,
                    InstructorID = instructors.Single(i => i.LastName == "Zheng").ID
                    },
                new CourseAssignment {
                    CourseID = courses.Single(c => c.Title == "Calculus" ).CourseID,
                    InstructorID = instructors.Single(i => i.LastName == "Fakhouri").ID
                    },
                new CourseAssignment {
                    CourseID = courses.Single(c => c.Title == "Trigonometry" ).CourseID,
                    InstructorID = instructors.Single(i => i.LastName == "Harui").ID
                    },
                new CourseAssignment {
                    CourseID = courses.Single(c => c.Title == "Composition" ).CourseID,
                    InstructorID = instructors.Single(i => i.LastName == "Abercrombie").ID
                    },
                new CourseAssignment {
                    CourseID = courses.Single(c => c.Title == "Literature" ).CourseID,
                    InstructorID = instructors.Single(i => i.LastName == "Abercrombie").ID
                    },
            };

            foreach (CourseAssignment ci in courseInstructors)
            {
                context.CourseAssignments.Add(ci);
            }
            context.SaveChanges();

            var enrollments = new Enrollment[]
            {
                new Enrollment {
                    StudentID = students.Single(s => s.LastName == "Alexander").ID,
                    CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID,
                    Grade = Grade.A
                },
                    new Enrollment {
                    StudentID = students.Single(s => s.LastName == "Alexander").ID,
                    CourseID = courses.Single(c => c.Title == "Microeconomics" ).CourseID,
                    Grade = Grade.C
                    },
                    new Enrollment {
                    StudentID = students.Single(s => s.LastName == "Alexander").ID,
                    CourseID = courses.Single(c => c.Title == "Macroeconomics" ).CourseID,
                    Grade = Grade.B
                    },
                    new Enrollment {
                        StudentID = students.Single(s => s.LastName == "Alonso").ID,
                    CourseID = courses.Single(c => c.Title == "Calculus" ).CourseID,
                    Grade = Grade.B
                    },
                    new Enrollment {
                        StudentID = students.Single(s => s.LastName == "Alonso").ID,
                    CourseID = courses.Single(c => c.Title == "Trigonometry" ).CourseID,
                    Grade = Grade.B
                    },
                    new Enrollment {
                    StudentID = students.Single(s => s.LastName == "Alonso").ID,
                    CourseID = courses.Single(c => c.Title == "Composition" ).CourseID,
                    Grade = Grade.B
                    },
                    new Enrollment {
                    StudentID = students.Single(s => s.LastName == "Anand").ID,
                    CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID
                    },
                    new Enrollment {
                    StudentID = students.Single(s => s.LastName == "Anand").ID,
                    CourseID = courses.Single(c => c.Title == "Microeconomics").CourseID,
                    Grade = Grade.B
                    },
                new Enrollment {
                    StudentID = students.Single(s => s.LastName == "Barzdukas").ID,
                    CourseID = courses.Single(c => c.Title == "Chemistry").CourseID,
                    Grade = Grade.B
                    },
                    new Enrollment {
                    StudentID = students.Single(s => s.LastName == "Li").ID,
                    CourseID = courses.Single(c => c.Title == "Composition").CourseID,
                    Grade = Grade.B
                    },
                    new Enrollment {
                    StudentID = students.Single(s => s.LastName == "Justice").ID,
                    CourseID = courses.Single(c => c.Title == "Literature").CourseID,
                    Grade = Grade.B
                    }
            };

            foreach (Enrollment e in enrollments)
            {
                var enrollmentInDataBase = context.Enrollments.Where(
                    s =>
                            s.Student.ID == e.StudentID &&
                            s.Course.CourseID == e.CourseID).SingleOrDefault();
                if (enrollmentInDataBase == null)
                {
                    context.Enrollments.Add(e);
                }
            }
            context.SaveChanges();
        }
    }
}

Thêm migration

Lưu các thay đổi của bạn và build dự án. Sau đó mở cửa sổ lệnh trong thư mục dự án và nhập lệnh migrations add (chưa thực hiện lệnh update-database):

dotnetcli
dotnet ef migrations add ComplexDataModel

Bạn nhận được cảnh báo về khả năng mất dữ liệu.

text
An operation was scaffolded that may result in the loss of data. Please review the migration for accuracy.
Done. To undo this action, use 'ef migrations remove'

Đôi khi khi bạn thực thi migrations với dữ liệu hiện có, bạn cần chèn dữ liệu stub (dữ liệu đại diện) vào cơ sở dữ liệu để thỏa mãn các ràng buộc foreign key. Mã được tạo ra trong phương thức Up thêm foreign key DepartmentID không nullable vào bảng Course. Nếu đã có các hàng trong bảng Course khi mã chạy, thao tác AddColumn sẽ thất bại vì SQL Server không biết giá trị nào để đặt vào cột không thể null.

Để làm cho migration này hoạt động với dữ liệu hiện có, bạn phải thay đổi mã để cung cấp giá trị mặc định cho cột mới, và tạo một khoa stub có tên "Temp" để đóng vai trò là khoa mặc định.

csharp
migrationBuilder.Sql("INSERT INTO dbo.Department (Name, Budget, StartDate) VALUES ('Temp', 0.00, GETDATE())");
// Default value for FK points to department created above, with
// defaultValue changed to 1 in following AddColumn statement.

migrationBuilder.AddColumn<int>(
    name: "DepartmentID",
    table: "Course",
    nullable: false,
    defaultValue: 1);

Thay đổi connection string

Bây giờ bạn có mã mới trong lớp DbInitializer thêm seed data cho các entity mới vào cơ sở dữ liệu trống. Để EF tạo cơ sở dữ liệu trống mới, hãy thay đổi tên cơ sở dữ liệu trong connection string trong appsettings.json thành ContosoUniversity3 hoặc một tên khác mà bạn chưa sử dụng trên máy tính đang sử dụng:

json
{
  "ConnectionStrings": {
    "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=ContosoUniversity3;Trusted_Connection=True;MultipleActiveResultSets=true"
  },

Cập nhật cơ sở dữ liệu

Sau khi bạn đã thay đổi tên cơ sở dữ liệu hoặc xóa cơ sở dữ liệu, chạy lệnh database update trong cửa sổ lệnh để thực thi các migrations:

dotnetcli
dotnet ef database update

Chạy ứng dụng để gây ra việc chạy phương thức DbInitializer.Initialize và nạp cơ sở dữ liệu mới.

Mở cơ sở dữ liệu trong SSOX (SQL Server Object Explorer) như bạn đã làm trước đó, và mở rộng node Tables để xem rằng tất cả các bảng đã được tạo.

Chạy ứng dụng để kích hoạt mã initializer nạp dữ liệu vào cơ sở dữ liệu.

Nhấp chuột phải vào bảng CourseAssignment và chọn View Data để xác minh rằng nó có dữ liệu.

Lấy mã nguồn

Tải xuống hoặc xem ứng dụng hoàn chỉnh.