EF Code-First - Cấu hình mối quan hệ một-nhiều

Cấu hình mối quan hệ một-nhiều trong Entity Framework

Ở phần này, chúng ta sẽ tìm hiểu cách cấu hình mối quan hệ một-nhiều giữa hai thực thể trong Entity Framework 6.x bằng cách sử dụng phương pháp tiếp cận Code First.

Chúng ta sẽ cấu hình mối quan hệ một-nhiều giữa các thực thể StudentGrade - một lớp có nhiều sinh viên.

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

Sau khi thực thi mối quan hệ một-nhiều trong các thực thể trên, các bảng cơ sở dữ liệu cho Student Grade sẽ giống như bên dưới. 

one-to-one relationship in code first


Các quy ước cho mối quan hệ một-nhiều

Có một số quy ước nhất định trong Entity Framework mà nếu các lớp thực thể tuân theo sẽ tự động dẫn đến mối quan hệ một-nhiều giữa hai bảng trong cơ sở dữ liệu. Bạn không cần phải cấu hình bất cứ điều gì khác.

Hãy xem một ví dụ về các quy ước tạo ra mối quan hệ một-nhiều trong Entity Framework Code First.

Quy ước 1:

Chúng tôi muốn thiết lập mối quan hệ một-nhiều giữa các thực thể StudentGrade. Có nghĩa là mỗi thực thể Student sẽ trỏ đến một thực thể Grade.

Điều này có thể thực hiện bằng cách tạo một thuộc tính điều hướng tham chiếu kiểu Grade trong lớp thực thể Student, như ví dụ bên dưới.

public class Student
{
    public int Id { get; set; }
    public string Name { 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; }
}

Trong ví dụ trên, lớp Student có một thuộc tính điều hướng tham chiếu của lớp Grade. Vì vậy, có thể có nhiều học sinh trong một lớp.

Điều này sẽ dẫn đến mối quan hệ một-nhiều giữa bảng Students và bảng Grades trong cơ sở dữ liệu, trong đó bảng Students có khóa ngoại Grade_GradeId như hình bên dưới.

one-to-one relationship in code first

Lưu ý rằng thuộc tính tham chiếu là nullable, vì vậy nó tạo ra một cột khóa ngoại Grade_GradeId có thể null trong bảng Students.

Quy ước 2:

Có một thuộc tính điều hướng kiểu tập hợp trong thực thể chính như dưới đây.

public class Student
{
    public int StudentId { get; set; }
    public string StudentName { 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; } 
}

Trong ví dụ trên, thực thể Grade có một thuộc tính điều hướng tập hợp kiểu ICollection<Student>.

Điều này cũng dẫn đến mối quan hệ một-nhiều giữa các thực thể StudentGrade. Ví dụ này tạo ra kết quả tương tự trong cơ sở dữ liệu như quy ước 1.

Quy ước 3:

Có các thuộc tính điều hướng ở cả hai đầu cũng sẽ dẫn đến mối quan hệ một-nhiều, như được trình bày bên dưới.

public class Student
{
    public int Id { get; set; }
    public string Name { 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> Student { get; set; }
}

Trong ví dụ trên, lớp thực thể Student có một thuộc tính điều hướng tham chiếu kiểu Grade và lớp thực thể Grade có một thuộc tính điều hướng tập hợp kiểu ICollection<Student> dẫn đến mối quan hệ một-nhiều. Ví dụ này tạo ra kết quả tương tự trong cơ sở dữ liệu như quy ước 1.

Quy ước 4:

Một mối quan hệ được chỉ định đầy đủ ở cả hai đầu sẽ tạo ra mối quan hệ một-nhiều, như hình dưới đây.

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

public class Grade
{

    public int GradeId { get; set; }
    public string GradeName { get; set; }
    
    public ICollection<Student> Student { get; set; }
}

Trong ví dụ trên, lớp thực thể Student có thuộc tính khóa ngoại GradeId và thuộc tính tham chiếu của nó là Grade. Điều này sẽ tạo mối quan hệ một-nhiều với cột khóa ngoại NotNull trong bảng Students, như được hiển thị bên dưới.

one-to-one relationship in code first

Nếu kiểu dữ liệu của GradeId là số nguyên nullable, thì nó sẽ tạo khóa ngoại null.

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

    public int? GradeId { get; set; }
    public Grade Grade { get; set; }
}

Đoạn mã trên sẽ tạo một cột GradeId có thể null trong cơ sở dữ liệu vì chúng tôi đã sử dụng kiểu Nullable<int> (int? là viết tắt của Nullable<int>)


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

Nói chung, bạn không cần định cấu hình mối quan hệ một-nhiều trong Entity Framework vì các quy ước ở trên sẽ giúp bạn làm điều này.

Tuy nhiên, bạn có thể định cấu hình các mối quan hệ bằng Fluent API tại một nơi để làm cho nó dễ bảo trì hơn.

Hãy xem các lớp thực thể StudentGrade sau.

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

    public int CurrentGradeId { get; set; }
    public Grade CurrentGrade { 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; }
}

Bạn có thể 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ư ví dụ bên dưới.

public class SchoolContext : DbContext
{
    public DbSet<Student> Students { get; set; }
    public DbSet<Grade> Grades { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        // configures one-to-many relationship
        modelBuilder.Entity<Student>()
            .HasRequired<Grade>(s => s.CurrentGrade)
            .WithMany(g => g.Students)
            .HasForeignKey<int>(s => s.CurrentGradeId);
    }
}

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

  • Đầu tiên chúng ta cần chỉ định cấu hình cho lớp thực thể nào bằng khai báo modelBuilder.Entity<student>() - cấu hình cho thực thể Student.
  • Khai báo .HasRequired<Grade>(s => s.CurrentGrade) chỉ định rằng lớp thực thể Student yêu cầu thuộc tính CurrentGrade. Điều này sẽ tạo một cột khóa ngoài NotNull trong DB.
  • Khai báo .WithMany(g => g.Students) chỉ định rằng lớp thực thể Grade có nhiều thực thể Student.
  • Bây giờ, nếu thực thể Student không tuân theo quy ước thuộc tính Id cho khóa ngoại, thì chúng ta có thể chỉ định tên của khóa ngoại bằng phương thức HasForeignKey. Khai báo .HasForeignKey<int>(s => s.CurrentGradeId) chỉ định thuộc tính khóa ngoại trong thực thể Student.

Ngoài ra, bạn cũng có thể cấu hình mối quan hệ bắt đầu với thực thể Grade thay vì thực thể Student như ở ví dụ trên. Ví dụ sau đây tạo ra kết quả tương tự như trên.

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

Ví dụ trên sẽ tạo các bảng sau trong cơ sở dữ liệu.

one-to-one relationship in code first

Cấu hình khóa ngoại NotNull bằng Fluent API

Trong quy ước 1, chúng ta đã thấy rằng nó tạo ra một mối quan hệ một-nhiều tùy chọn, từ đó tạo ra một cột khóa ngoại có thể Null trong cơ sở dữ liệu.

Để biến nó thành cột NotNull, hãy sử dụng phương thức HasRequired() như bên dưới.

modelBuilder.Entity<Student>()
    .HasRequired<Grade>(s => s.CurrentGrade)
    .WithMany(g => g.Students);
    

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

Cascade Delete có nghĩa là tự động xóa các các bản ghi con liên quan khi bản ghi cha bị xóa. Ví dụ, nếu lớp bị xóa thì tất cả các sinh viên trong lớp đó cũng sẽ bị xóa tự động. Ví dụ sau cấu hình Cascade Delete bằng phương thức WillCascadeOnDelete.

modelBuilder.Entity<Grade>()
    .HasMany<Student>(g => g.Students)
    .WithRequired(s => s.CurrentGrade)
    .WillCascadeOnDelete();
    
Lưu ý: Chúng tôi khuyến nghị bạn không nên sử dụng cascade delete để tránh gặp phải vấn đề mất mát dữ liệu ngoài ý muốn.