Cấu hình trong Entity Framework Core

Bạn đã tìm hiểu về các quy ước mặc định trong EF Core trong hướng dẫn trước. Đôi khi chúng ta muốn tùy chỉnh ánh xạ thực thể thành bảng cơ sở dữ liệu và không muốn tuân theo các quy ước mặc định.

EF Core cho phép chúng ta cấu hình các lớp thực thể để tùy chỉnh ánh xạ mô hình EF thành cơ sở dữ liệu. Mẫu lập trình này được gọi là Quy ước về Cấu hình.

Có hai cách để cấu hình các lớp thực thể trong EF Core (giống như trong EF 6).

  1. Cấu hình bằng cách sử dụng các attribute chú thích dữ liệu.
  2. Cấu hình bằng cách sử dụng Fluent API.

Attribute chú thích dữ liệu trong EF Core

Chú thích dữ liệu là một phương thức cấu hình dựa trên các attribute trong đó các attribute .NET khác nhau có thể được áp dụng cho các lớp thực thể và các thuộc tính để cấu hình mô hình.

Lưu ý: Bài viết sử dụng thuật ngữ gốc là attribute (thuộc tính) để tránh nhầm lẫn với các thuộc tính (property) của lớp.

Các attribute chú thích dữ liệu không chỉ dành riêng cho Entity Framework, mà chúng cũng được sử dụng trong ASP.NET MVC.

Đây là lý do tại sao các attribute này được đặt trong namespace riêng biệt là System.ComponentModel.DataAnnotations.

Ví dụ sau đây cho thấy cách các attribute chú thích dữ liệu có thể được áp dụng cho một lớp thực thể và các thuộc tính để ghi đè các quy ước mặc định.


[Table("StudentInfo")]
public class Student
{
    public Student() { }
        
    [Key]
    public int SID { get; set; }

    [Column("Name", TypeName="ntext")]
    [MaxLength(20)]
    public string StudentName { get; set; }

    [NotMapped]
    public int? Age { get; set; }
        
        
    public int StdId { get; set; }

    [ForeignKey("StdId")]
    public virtual Standard Standard { get; set; }
}

Các attribute chú thích dữ liệu trong EF 6 và EF Core thì giống nhau. Do đó bạn có thể truy cập bài viết Attribute chú thích dữ liệu trong Entity Framework để biết thêm thông tin.

Attribute chú thích dữ liệu trong Entity Framework | Comdy
Attribute chú thích dữ liệu trong Entity Framework là gì? Có những loại attribute chú thích dữ liệu nào?

Fluent API trong EF Core

Một cách khác để cấu hình các lớp thực thể là bằng cách sử dụng Entity Framework Fluent API. EF Fluent API dựa trên mẫu thiết kế Fluent API (hay còn gọi là giao diện thông thạo) trong đó kết quả được tạo thành từ một chuỗi phương thức.

Trong Entity Framework Core, lớp ModelBuilder hoạt động như một Fluent API. Bằng cách sử dụng nó, chúng ta có thể cấu hình nhiều thứ khác nhau, vì nó cung cấp nhiều tùy chọn cấu hình hơn các attribute chú thích dữ liệu.

Entity Framework Core Fluent API cấu hình các khía cạnh sau của một mô hình:

  1. Cấu hình mô hình: Cấu hình mô hình EF để ánh xạ vào cơ sở dữ liệu. Cấu hình lượng đồ mặc định, các hàm DB, các thuộc tính và thực thể chú thích dữ liệu bổ sung được loại trừ khỏi ánh xạ.
  2. Cấu hình thực thể: Cấu hình thực thể cho bảng và ánh xạ mối quan hệ, ví dụ: khóa chính, khóa thay thể (AlternateKey), Index, tên bảng, các mối quan hệ như một-một, một-nhiều, nhiều-nhiều, v.v.
  3. Cấu hình thuộc tính: Cấu hình thuộc tính để ánh xạ cột, ví dụ tên cột, giá trị mặc định, nullable, khóa ngoại, kiểu dữ liệu, cột chống xung đột, v.v.

Bảng sau liệt kê các phương thức quan trọng cho từng loại cấu hình.

Phương thức Mô tả
Cấu hình mô hình
HasDbFunction Cấu hình chức năng cơ sở dữ liệu.
HasDefaultSchema Chỉ định lược đồ cơ sở dữ liệu mặc định.
HasAnnotation Thêm hoặc cập nhật các attribute chú thích dữ liệu trên thực thể.
HasSequence Cấu hình chuỗi cơ sở dữ liệu.
Cấu hình thực thể
HasAlternateKey Cấu hình khóa thay thế trong mô hình EF cho thực thể.
HasIndex Cấu hình thuộc tính Index cho kiểu thực thể.
HasKey Cấu hình thuộc tính khóa chính cho kiểu thực thể.
HasMany Cấu hình phần nhiều của mối quan hệ một-nhiều hoặc nhiều-nhiều.
HasOne Cấu hình phần một của mối quan hệ một-nhiều hoặc một-một.
Ignore Cấu hình lớp thực thể hoặc thuộc tính không được ánh xạ vào bảng hoặc cột trong cơ sở dữ liệu.
OwnsOne Cấu hình mối quan hệ trong đó thực thể đích được sở hữu bởi thực thể này. Giá trị khóa thực thể đích được truyền từ thực thể mà nó thuộc về.
ToTable Cấu hình tên bảng cho thực thể.
Cấu hình thuộc tính
HasColumnName Cấu hình tên cột tương ứng của một thuộc tính trong cơ sở dữ liệu.
HasColumnType Cấu hình kiểu dữ liệu của cột tương ứng của một thuộc tính trong cơ sở dữ liệu.
HasComputedColumnSql Cấu hình thuộc tính để ánh xạ tới cột được tính toán trong cơ sở dữ liệu.
HasDefaultValue Cấu hình giá trị mặc định cho cột mà thuộc tính ánh xạ.
HasDefaultValueSql Cấu hình biểu thức giá trị mặc định cho cột mà thuộc tính ánh xạ.
HasField Chỉ định trường sao lưu được sử dụng với thuộc tính.
HasMaxLength Cấu hình độ dài tối đa của dữ liệu có thể được lưu trữ trong một thuộc tính.
IsConcurrencyToken Cấu hình thuộc tính được sử dụng làm mã thông báo xung đột.
IsRequired Cấu hình thuộc tính bắt buộc nhập dữ liệu khi gọi phương thức SaveChanges.
IsRowVersion Cấu hình thuộc tính được sử dụng để phát hiện xung đột.
IsUnicode Cấu hình thuộc tính chuỗi có thể chứa các ký tự unicode hoặc không.
ValueGeneratedNever Cấu hình một thuộc tính không thể có giá trị được tạo tự động khi thực thể được lưu.
ValueGeneratedOnAdd Cấu hình rằng thuộc tính có giá trị được tạo tự động khi lưu thực thể mới.
ValueGeneratedOnAddOrUpdate Cấu hình rằng thuộc tính có giá trị được tạo tự động khi lưu thực thể mới hoặc cập nhật thực thể hiện có.
ValueGeneratedOnUpdate Cấu hình rằng thuộc tính có giá trị được tạo tự động khi cập nhật thực thể hiện có.

Cấu hình Fluent API trong EF Core

Ghi đè phương thức OnModelCreating và sử dụng tham số modelBuilder kiểu ModelBuilder để cấu hình các lớp thực thể như ví dụ bên dưới.


public class SchoolDBContext: DbContext 
{
    public DbSet<Student> Students { get; set; }
        
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        //Write Fluent API configurations here

        //Property Configurations
        modelBuilder.Entity<Student>()
                .Property(s => s.StudentId)
                .HasColumnName("Id")
                .HasDefaultValue(0)
                .IsRequired();
    }
}

Trong ví dụ trên, thể hiện của ModelBuilder được sử dụng để cấu hình một thuộc tính bằng cách gọi một chuỗi nhiều phương thức.

Nó cấu hình thuộc tính StudentId của thực thể Student:

  • StudentId có tên là Id trong cơ sở dữ liệu bằng cách sử dụng phương thức HasColumnName
  • StudentId có giá trị mặc định là 0 bằng cách sử dụng phương thức HasDefaultValue
  • StudentId cho phép nullable bằng phương thức IsRequired

Các cấu hình trên được thực hiện trong một câu lệnh thay vì nhiều câu lệnh. Điều này làm tăng khả năng đọc và cũng mất ít thời gian hơn để viết so với nhiều câu lệnh, như được trình bày bên dưới.


//Fluent API method chained calls
modelBuilder.Entity<Student>()
        .Property(s => s.StudentId)
        .HasColumnName("Id")
        .HasDefaultValue(0)
        .IsRequired();

//Separate method calls
modelBuilder.Entity<Student>().Property(s => s.StudentId).HasColumnName("Id");
modelBuilder.Entity<Student>().Property(s => s.StudentId).HasDefaultValue(0);
modelBuilder.Entity<Student>().Property(s => s.StudentId).IsRequired();
Lưu ý: Cấu hình Fluent API có quyền ưu tiên cao hơn các attribute chú thích dữ liệu.

Cấu hình mối quan hệ một-nhiều bằng Fluent API trong EF Core

Bạn đã tìm hiểu về các quy ước cho mối quan hệ một-nhiều trong EF Core. Nói chung, bạn không cần cấu hình mối quan hệ một-nhiều vì EF Core có đủ các quy ước sẽ tự động cấu hình chúng.

Tuy nhiên, bạn có thể sử dụng Fluent API để cấu hình mối quan hệ một-nhiều nếu bạn muốn tất cả các cấu hình trong EF sử dụng Fluent API để dễ dàng bảo trì.

Entity Framework Core giúp dễ dàng cấu hình các mối quan hệ bằng Fluent API. Hãy xem các lớp thực thể StudentGrade sau đây trong đó thực thể Grade chứa nhiều thực thể Student.


public class Student
{
    public int Id { get; set; }
    public string Name { get; set; }

    public int CurrentGradeId { get; set; }
    public Grade Grade { get; set; }
}

public class Grade
{
    public int GradeId { get; set; }
    public string GradeName { get; set; }
    public string Section { get; set; }

    public ICollection<Student> Students { get; set; }
}

Cấu hình mối quan hệ một-nhiều cho các thực thể trên bằng Fluent API bằng cách ghi đè phương thức OnModelCreating trong lớp Context, như được hiển thị bên dưới.


public class SchoolContext : DbContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer("Server=.\\SQLEXPRESS;Database=EFCore-SchoolDB;Trusted_Connection=True");
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Student>()
            .HasOne<Grade>(s => s.Grade)
            .WithMany(g => g.Students)
            .HasForeignKey(s => s.CurrentGradeId);
    }

    public DbSet<Grade> Grades { get; set; }
    public DbSet<Student> Students { get; set; }
}

Trong ví dụ trên, đoạn mã sau cấu hình mối quan hệ một-nhiều:


modelBuilder.Entity<Student>()
    .HasOne<Grade>(s => s.Grade)
    .WithMany(g => g.Students)
    .HasForeignKey(s => s.CurrentGradeId);

Bây giờ, để ánh xạ điều này vào trong cơ sở dữ liệu, thực hiện các lệnh chuyển đổi , add-migration <name>update-database. Cơ sở dữ liệu sẽ có hai bảng có mối quan hệ một-nhiều như dưới đây.

Cấu hình mối quan hệ một nhiều bằng Fluent API trong EF Core

Chúng ta hãy hiểu mã trên từng bước.

  • Đầu tiên, chúng ta cần bắt đầu cấu hình với một lớp thực thể, Student hoặc Grade. Vì vậy, modelBuilder.Entity<student>() bắt đầu với thực thể Student.
  • Sau đó, .HasOne<Grade>(s => s.Grade) xác định rằng thực thể Student có một thuộc tính kiểu Grade được đặt tên là Grade.
  • Bây giờ, chúng ta cần cấu hình đầu kia của mối quan hệ, thực thể Grade. .WithMany(g => g.Students) xác định rằng lớp thực thể Grade có nhiều thực thể Student.
  • .HasForeignKey<int>(s => s.CurrentGradeId); chỉ định tên của thuộc tính khóa ngoại CurrentGradeId. Đây là tùy chọn. Chỉ sử dụng nó khi bạn có thuộc tính khóa ngoại Id trong lớp phụ thuộc.

Hình dưới đây minh họa các bước trên:

Cấu hình mối quan hệ một nhiều bằng Fluent API trong EF Core

Ngoài ra, bạn có thể bắt đầu cấu hình mối quan hệ với thực thể Grade thay vì thực thể Student, như ví dụ bên dưới.


modelBuilder.Entity<Grade>()
    .HasMany<Student>(g => g.Students)
    .WithOne(s => s.Grade)
    .HasForeignKey(s => s.CurrentGradeId);

Cấu hình Cascade Delete bằng Fluent API

Cascade Delete sẽ tự động xóa bản ghi con khi bản ghi cha liên quan bị xóa. Ví dụ, nếu một bản ghi của Grade bị xóa, thì tất cả các bản ghi của thực thể Students liên quan đến Grade đó cũng sẽ tự động bị xóa khỏi cơ sở dữ liệu.

Sử dụng phương thức OnDelete để cấu hình cascade delete giữa các thực thể StudentGrade, như ví dụ bên dưới.


modelBuilder.Entity<Grade>()
    .HasMany<Student>(g => g.Students)
    .WithOne(s => s.Grade)
    .HasForeignKey(s => s.CurrentGradeId)
    .OnDelete(DeleteBehavior.Cascade);

Phương thức OnDelete() sử dụng tham số DeleteBehavior. Bạn có thể chỉ định bất kỳ giá trị nào sau đây của DeleteBehavior, dựa trên yêu cầu của bạn.

  • Cascade: Các thực thể phụ thuộc sẽ bị xóa khi thực thể chính bị xóa.
  • ClientSetNull: Các giá trị của các thuộc tính khóa ngoại trong các thực thể phụ thuộc sẽ được thiết lập thành null.
  • Restrict: Ngăn chặn Cascade Delete.
  • SetNull: Các giá trị của các thuộc tính khóa ngoại trong các thực thể phụ thuộc sẽ được thiết lập thành null.

Cấu hình mối quan hệ một-một bằng Fluent API trong EF Core

Ở phần này, bạn sẽ tìm hiểu cách cấu hình mối quan hệ một-một giữa hai thực thể bằng Fluent API, nếu chúng không tuân theo các quy ước của EF Core.

Nói chung, bạn không cần phải cấu hình mối quan hệ một-một cách thủ công vì EF Core có các quy ước cho các mối quan hệ một-một.

Tuy nhiên, nếu các thuộc tính khóa hoặc khóa ngoại không tuân theo quy ước, thì bạn có thể sử dụng các attribute chú thích dữ liệu hoặc Fluent API để cấu hình mối quan hệ một-một giữa hai thực thể.

Chúng ta sẽ cấu hình mối quan hệ một-một giữa các thực thể StudentStudentAddress sau đây không tuân theo quy ước khóa ngoại.


public class Student
{
    public int Id { get; set; }
    public string Name { get; set; }
       
    public StudentAddress Address { get; set; }
}

public class StudentAddress
{
    public int StudentAddressId { get; set; }
    public string Address { get; set; }
    public string City { get; set; }
    public string State { get; set; }
    public string Country { get; set; }

    public int AddressOfStudentId { get; set; }
    public Student Student { get; set; }
}

Để cấu hình mối quan hệ một-một sử dụng EF Core Fluent API, chúng ta sẽ sử dụng các phương thức HasOne, WithOneHasForeignKey như ví dụ dưới đây.


public class SchoolContext : DbContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer("Server=.\\SQLEXPRESS;Database=EFCore-SchoolDB;Trusted_Connection=True");
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Student>()
            .HasOne<StudentAddress>(s => s.Address)
            .WithOne(ad => ad.Student)
            .HasForeignKey<StudentAddress>(ad => ad.AddressOfStudentId);
    }

    public DbSet<Student> Students { get; set; }
    public DbSet<StudentAddress> StudentAddresses { get; set; }
}

Trong ví dụ trên, đoạn mã sau cấu hình mối quan hệ một-một.


modelBuilder.Entity<Student>()
    .HasOne<StudentAddress>(s => s.Address)
    .WithOne(ad => ad.Student)
    .HasForeignKey<StudentAddress>(ad => ad.AddressOfStudentId);

Hãy hiểu nó từng bước một.

  • modelBuilder.Entity<Student>() bắt đầu cấu hình từ thực thể Student.
  • .HasOne<StudentAddress>(s => s.Address) chỉ định rằng thực thể Student có một thuộc tính điều hướng tham chiếu kiểu StudentAddress.
  • .WithOne(ad => ad.Student) cấu hình đầu kia của mối quan hệ, thực thể StudentAddress. Nó xác định rằng thực thể StudentAddress có một thuộc tính điều hướng tham chiếu kiểu Student.
  • .HasForeignKey<StudentAddress>(ad => ad.AddressOfStudentId) chỉ định tên thuộc tính khóa ngoại.

Bây giờ, để ánh xạ điều này vào trong cơ sở dữ liệu, thực hiện các lệnh chuyển đổi add-migration <name>update-database. Cơ sở dữ liệu sẽ có hai bảng có mối quan hệ một-một như dưới đây.

Cấu hình mối quan hệ một-một bằng Fluent API trong EF Core

Hình dưới đây minh họa cấu hình Fluent API cho mối quan hệ một-một.

Cấu hình mối quan hệ một-một bằng Fluent API trong EF Core

Bạn cũng có thể bắt đầu cấu hình với thực thể StudentAddress theo cách tương tự như dưới đây.


modelBuilder.Entity<StudentAddress>()
    .HasOne<Student>(ad => ad.Student)
    .WithOne(s => s.Address)
    .HasForeignKey<StudentAddress>(ad => ad.AddressOfStudentId);

Cấu hình mối quan hệ nhiều-nhiều bằng Fluent API trong EF Core

Ở phần này, bạn sẽ tìm hiểu cách cấu hình mối quan hệ nhiều-nhiều giữa hai thực thể bằng Fluent API trong Entity Framework Core.

Chúng ta hãy thực hiện mối quan hệ nhiều-nhiều giữa các thực thể StudentCourse, trong đó một sinh viên có thể đăng ký nhiều khóa học và theo cách tương tự, một khóa học có thể được nhiều sinh viên tham gia.


public class Student
{
    public int StudentId { get; set; }
    public string Name { get; set; }
}

public class Course
{
    public int CourseId { get; set; }
    public string CourseName { get; set; }
    public string Description { get; set; }
}

Mối quan hệ nhiều-nhiều trong cơ sở dữ liệu được thể hiện bằng một bảng tham gia bao gồm các khóa ngoại của cả hai bảng. Ngoài ra, các khóa ngoại này cũng là khóa chính tổng hợp.

Cấu hình mối quan hệ nhiều-nhiều bằng Fluent API trong EF Core

Không có quy ước mặc định nào có sẵn trong Entity Framework Core tự động cấu hình mối quan hệ nhiều-nhiều. Bạn phải cấu hình nó bằng Fluent API.

Trong Entity Framework 6.x hoặc trước đó, EF API được sử dụng để tạo bảng tham gia cho các mối quan hệ nhiều-nhiều.

Chúng ta không cần phải tạo một thực thể tham gia cho một bảng tham gia (tuy nhiên chúng ta có thể tạo một thực thể tham gia rõ ràng trong EF 6).

Trong Entity Framework Core, điều này chưa được thực hiện. Chúng ta phải tạo một lớp thực thể tham gia cho bảng tham gia.

Thực thể tham gia cho các thực thể StudentCourse ở trên nên bao gồm một thuộc tính khóa ngoài và thuộc tính điều hướng tham chiếu cho mỗi thực thể.

Các bước để định cấu hình mối quan hệ nhiều-nhiều sẽ như sau:

  1. Định nghĩa lớp thực thể tham gia có thuộc tính khóa ngoại và thuộc tính điều hướng tham chiếu cho mỗi thực thể trong mối quan hệ.
  2. Định nghĩa mối quan hệ một-nhiều giữa hai thực thể trong mối quan hệ và thực thể tham gia, bằng cách thêm một thuộc tính điều hướng tập hợp trong các thực thể ở cả hai bên (trong trường hợp này là StudentCourse).
  3. Cấu hình cả hai khóa ngoại trong thực thể tham gia dưới dạng khóa tổng hợp bằng Fluent API.

Như vậy, trước hết chúng ta sẽ định nghĩa thực thể tham gia StudentCourse, như được trình bày bên dưới.


public class StudentCourse
{
    public int StudentId { get; set; }
    public Student Student { get; set; }

    public int CourseId { get; set; }
    public Course Course { get; set; }
}

Trên thực thể tham gia StudentCourse có các thuộc tính điều hướng tham chiếu StudentCourse và các thuộc tính khóa ngoại StudentIdCourseId tương ứng (các thuộc tính khóa ngoại tuân theo quy ước).

Bây giờ, chúng ta cần cấu hình hai mối quan hệ một-nhiều riêng biệt giữa thực thể Student -> StudentCourse và thực thể Course -> StudentCourse.

Chúng ta có thể làm điều đó bằng cách tuân theo quy ước cho mối quan hệ một-nhiều như ví dụ dưới đây.


public class Student
{
    public int StudentId { get; set; }
    public string Name { get; set; }

    public IList<StudentCourse> StudentCourses { get; set; }
}

public class Course
{
    public int CourseId { get; set; }
    public string CourseName { get; set; }
    public string Description { get; set; }

    public IList<StudentCourse> StudentCourses { get; set; }
}

Như bạn có thể thấy ở trên, các thực thể StudentCourse có một thuộc tính điều hướng tập hợp kiểu IList<StudentCourse>.

Thực thể StudentCourse đã có thuộc tính khóa ngoại và thuộc tính điều hướng cho cả hai thực thể StudentCourse.

Điều này làm cho nó trở thành mối quan hệ một-nhiều được định nghĩa đầy đủ giữa Student -> StudentCourseCourse -> StudentCourse.

Bây giờ, các khóa ngoại phải là khóa chính tổng hợp trong bảng tham gia. Điều này chỉ có thể được cấu hình bằng Fluent API, như ví dụ bên dưới.


public class SchoolContext : DbContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer("Server=.\\SQLEXPRESS;Database=EFCore-SchoolDB;Trusted_Connection=True");
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<StudentCourse>().HasKey(sc => new { sc.StudentId, sc.CourseId });
    }
    
    public DbSet<Student> Students { get; set; }
    public DbSet<Course> Courses { get; set; }
    public DbSet<StudentCourse> StudentCourses { get; set; }
}

Trong ví dụ trên, đoạn mã sau cấu hình StudentIdCourseId làm khóa tổng hợp:


modelBuilder.Entity<StudentCourse>().HasKey(sc => new { sc.StudentId, sc.CourseId });

Đây là cách bạn có thể cấu hình mối quan hệ nhiều-nhiều nếu các thực thể tuân theo các quy ước cho các mối quan hệ một-nhiều với thực thể tham gia.

Giả sử rằng các tên thuộc tính khóa ngoại không tuân theo quy ước (ví dụ SID thay vì StudentId và CID thay vì CourseId), thì bạn có thể định cấu hình nó bằng Fluent API, như ví dụ bên dưới.


modelBuilder.Entity<StudentCourse>().HasKey(sc => new { sc.SId, sc.CId });

modelBuilder.Entity<StudentCourse>()
    .HasOne<Student>(sc => sc.Student)
    .WithMany(s => s.StudentCourses)
    .HasForeignKey(sc => sc.SId);


modelBuilder.Entity<StudentCourse>()
    .HasOne<Course>(sc => sc.Course)
    .WithMany(s => s.StudentCourses)
    .HasForeignKey(sc => sc.CId);


Bài viết liên quan:

2 kịch bản lưu dữ liệu trong Entity Framework Core là kịch bản được kết nối và kịch bản ngắt kết nối.

Tạo ứng dụng .NET Core Console đầu tiên và cấu hình sử dụng Entity Framework Core.

Truy vấn trong Entity Framework Core có gì mới? Truy vấn trong EF Core khác EF ở những điểm nào.