Cấu hình mối quan hệ trong Entity Framework
Trong cơ sở dữ liệu quan hệ, mối quan hệ là một liên kết tồn tại giữa các bảng của cơ sở dữ liệu quan hệ thông qua các khóa ngoại.
Khóa ngoại (Foreign Key) là một cột hoặc tổ hợp các cột được sử dụng để thiết lập và thực thi một liên kết giữa dữ liệu trong hai bảng.
Có ba loại mối quan hệ giữa các bảng và chúng khác nhau tùy thuộc vào cách định nghĩa các cột liên quan.
- Mối quan hệ một-nhiều
- Mối quan hệ nhiều-nhiều
- Mối quan hệ một-một
Để hiểu rõ hơn về mối quan hệ giữa các thực thể trong Entity Framwork bạn có thể xem bài viết:

Bài viết này sẽ hướng dẫn bạn cách cấu hình ba loại mối quan hệ trên trong Entity Framework Code First.
Cấu hình mối quan hệ một-một trong Entity Framework
Ở 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ể.
Chúng tôi sẽ thực hiện mối quan hệ một-một giữa thực thể Student
và StudentAddress
như sau:
public class Student
{
public int StudentId { get; set; }
public string StudentName { get; set; }
public virtual StudentAddress Address { get; set; }
}
public class StudentAddress
{
public int StudentAddressId { get; set; }
public string Address1 { get; set; }
public string Address2 { get; set; }
public string City { get; set; }
public int Zipcode { get; set; }
public string State { get; set; }
public string Country { get; set; }
public virtual Student Student { get; set; }
}
Mối quan hệ một-một xảy ra khi khóa chính của một bảng trở thành khóa chính và khóa ngoại của một bảng khác trong cơ sở dữ liệu quan hệ như SQL Server.
Vì vậy, chúng ta cần cấu hình các thực thể ở trên để EF tạo các bảng Students
và bảng StudentAddresses
trong DB.
Nó sẽ tạo cột StudentId
trong bảng Student
là khóa chính và cột StudentAddressId
trong bảng StudentAddresses
vừa là khóa chính vừa là khóa ngoại.
Cấu hình mối quan hệ một-một bằng cách sử dụng các attribute chú thích dữ liệu
Trong trường hợp này, chúng tôi sẽ sử dụng các thuộc tính chú thích dữ liệu trên các thực thể Student
và StudentAddress
để thiết lập mối quan hệ một-một.
Nếu bạn bỏ lỡ bài viết hướng dẫn cách sử dụng attribute chú thích dữ liệu thì có thể xem tại đây:

Thực thể Student
tuân theo quy ước mặc định của Code First vì nó có thuộc tính StudentId
sẽ là thuộc tính khóa.
Vì vậy, chúng ta không cần phải áp dụng bất kỳ attribute chú thích dữ liệu nào trên nó bởi vì EF sẽ tạo cột StudentId
là khóa chính của bảng Students
trong cơ sở dữ liệu.
Đối với thực thể StudentAddress
, chúng ta cần cấu hình StudentAddressId
vừa là khóa chính vừa là khóa ngoại.
Thuộc tính StudentAddressId
theo quy ước mặc định sẽ là khóa chính. Vì vậy, chúng ta không cần phải áp dụng bất kỳ attribute chú thích dữ liệu nào cho khóa chính.
Tuy nhiên, chúng ta cũng cần cấu hình nó là khóa ngoại của thực thể Student
.
Vì vậy, chúng ta sẽ khai báo [ForeignKey("Student")]
trên thuộc tính StudentAddressId
để cấu hình nó làm khóa ngoại cho thực thể Student
, như được trình bày dưới đây.
public class Student
{
public int StudentId { get; set; }
public string StudentName { get; set; }
public virtual StudentAddress Address { get; set; }
}
public class StudentAddress
{
[ForeignKey("Student")]
public int StudentAddressId { get; set; }
public string Address1 { get; set; }
public string Address2 { get; set; }
public string City { get; set; }
public int Zipcode { get; set; }
public string State { get; set; }
public string Country { get; set; }
public virtual Student Student { get; set; }
}
Như vậy là bạn đã sử dụng attribute chú thích dữ liệu để cấu hình mối quan hệ một-một giữa hai thực thể.
Lưu ý: Thực thể
Student
có thuộc tính điều hướngStudentAddress
và thực thểStudentAddress
có thuộc tính điều hướngStudent
. Với mối quan hệ một-một, một thực thểStudent
có thể được lưu trữ mà không có thực thểStudentAddress
nhưng ngược lại thì không thể. EF sẽ đưa ra một ngoại lệ nếu bạn cố lưu thực thểStudentAddress
mà không có thực thểStudent
.
Cấu hình mối quan hệ một-một bằng cách sử dụng Fluent API
Ở phần này, chúng tôi sẽ sử dụng Fluent API để định cấu hình mối quan hệ một-một giữa các thực thể Student
và StudentAddress
.
Nếu bạn bỏ lỡ bài viết hướng dẫn cách sử dụng Fluent API thì có thể xem ở đây:

Ví dụ sau đây thiết lập mối quan hệ một-một giữa Student
và StudentAddress
sử dụng Fluent API.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// Configure Student & StudentAddress entity
modelBuilder.Entity<Student>()
.HasOptional(s => s.Address) // Mark Address property optional in Student entity
.WithRequired(ad => ad.Student); // mark Student property as required in StudentAddress entity. Cannot save StudentAddress without Student
}
Trong ví dụ trên, chúng ta bắt đầu với thực thể Student
. Phương thức HasOptional()
cấu hình thuộc tính điều hướng Address
của thực thể Student
là tùy chọn (không bắt buộc khi lưu thực thể Student
).
Sau đó, phương thức WithRequired()
cấu hình thuộc tính điều hướng Student
của thực thể StudentAddress
là bắt buộc (bắt buộc phải có khi lưu thực thể StudentAddress
; nó sẽ đưa ra một ngoại lệ khi thực thể StudentAddress
được lưu mà không có thuộc tính điều hướng Student
). Điều này cũng sẽ làm cho cột StudentAddressId
trở thành khóa ngoại.
Như vậy là bạn đã biết cách cấu hình mối quan hệ một-một giữa hai thực thể bằng cách sử dụng Fluent API.
EF API sẽ tạo các bảng sau trong cơ sở dữ liệu.

Ở cách cấu hình trên, một thực thể Student
có thể được lưu mà không có thực thể StudentAddress
nhưng ngược lại thì không thể. EF sẽ đưa ra một ngoại lệ nếu bạn cố lưu thực thể StudentAddress
mà không có thực thể Student
.
Tuy nhiên chúng ta có thể cấu hình mối quan hệ một-một giữa các thực thể bằng Fluent API trong đó cả hai đầu đều bắt buộc.
Nghĩa là khi lưu một đối tượng thực thể Student
bắt buộc phải có đối tượng thực thể StudentAddress
và ngược lại.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// Configure StudentId as FK for StudentAddress
modelBuilder.Entity<Student>()
.HasRequired(s => s.Address)
.WithRequiredPrincipal(ad => ad.Student);
}
Trong ví dụ trên, phương thức HasRequired(s => s.Address)
cấu hình thuộc tính Address
của thực thể StudentAddress
là bắt buộc và phương thức WithRequiredPrincipal(ad => ad.Student)
ấu hình thuộc tính Student
của thực thể StudentAddress
là bắt buộc.
Do đó, khi bạn cố gắng lưu thực thể Student
mà không có thực thể StudentAddress
hoặc ngược lại thì EF sẽ ném ra một ngoại lệ.
Cấu hình mối quan hệ nhiều-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ệ nhiều-nhiều giữa các lớp thực thể Student
và Course
. Một sinh viên có thể tham gia nhiều khóa học và một khóa học có nhiều sinh viên.
Cấu hình mối quan hệ nhiều-nhiều bằng cách tuân theo quy ước mặc định của Code First
EF 6 có các quy ước mặc định cho mối quan hệ nhiều-nhiều. Bạn cần có một thuộc tính điều hướng kiểu tập hợp ở cả hai đầu.
Ví dụ, lớp Student
có thuộc tính điều hướng kiểu ICollection<Course>
và lớp Course
có thuộc tính điều hướng kiểu ICollection<Student>
để tạo mối quan hệ nhiều-nhiều giữa chúng mà không cần bất kỳ cấu hình nào, như được trình bày bên dưới:
public class Student
{
public Student()
{
this.Courses = new HashSet<Course>();
}
public int StudentId { get; set; }
[Required]
public string StudentName { get; set; }
public virtual ICollection<Course> Courses { get; set; }
}
public class Course
{
public Course()
{
this.Students = new HashSet<Student>();
}
public int CourseId { get; set; }
public string CourseName { get; set; }
public virtual ICollection<Student> Students { get; set; }
}
Sau đây là lớp Context bao gồm các thực thể Student
và Course
.
public class SchoolDBContext : DBContext
{
public SchoolDBContext() : base("SchoolDB-DataAnnotations")
{
}
public DbSet<Student> Students { get; set; }
public DbSet<Course> Courses { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
}
EF API sẽ tạo ra ba bảng là Students
, Courses
và bảng tham gia tên là StudentCourses
trong cơ sở dữ liệu. Bảng StudentCourses
sẽ bao gồm khóa chính của cả hai bảng Students
và Courses
– đó là Student_StudentId
và Course_CourseId
như hình dưới đây.

Lưu ý: EF tự động tạo bảng tham gia với tên của cả hai thực thể và hậu tố ‘s’.
Cấu hình mối quan hệ nhiều-nhiều bằng Fluent API
Như bạn đã thấy ở trên, các quy ước mặc định của Code First cho mối quan hệ nhiều-nhiều sẽ tạo ra bảng tham gia với các quy ước đặt tên mặc định.
Sử dụng Fluent API để tùy chỉnh tên bảng và tên cột tham gia, như được trình bày bên dưới:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Student>()
.HasMany<Course>(s => s.Courses)
.WithMany(c => c.Students)
.Map(cs =>
{
cs.MapLeftKey("StudentRefId");
cs.MapRightKey("CourseRefId");
cs.ToTable("StudentCourse");
});
}
Trong ví dụ trên, các phương thức HasMany()
và WithMany()
được sử dụng để cấu hình mối quan hệ nhiều-nhiều giữa các thực thể Student
và Course
.
Phương thức Map()
nhận tham số kiểu delegate Action, do đó chúng ta có thể truyền các biểu thức lambda để tùy chỉnh tên cột trong bảng tham gia.
Chúng ta có thể chỉ định tên thuộc tính khóa chính của bảng Student
trong phương thức MapLeftKey()
(chúng ta đã bắt đầu với thực thể Student
, vì vậy nó sẽ là bảng bên trái) và khóa chính của bảng Course
trong phương thức MapRightKey()
.
Phương thức ToTable()
chỉ định tên của bảng tham gia (trong trường hợp này là StudentCourse
).
Đoạn mã trên sẽ tạo bảng tham gia StudentCourse
với hai khóa chính là StudentRefId
, CourseRefId
và chúng cũng sẽ là khóa ngoại, như được hiển thị bên dưới:

Theo cách này, bạn có thể ghi đè các quy ước mặc định của Code First cho mối quan hệ nhiều-nhiều và tùy chỉnh tên bảng tham gia và các cột của nó.
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ể Student
và Grade
– 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; }
}
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ể Student
và Grade
. 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.

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ể Student
và Grade
. 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.

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ể Student
và Grade
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ínhCurrentGrade
. Đ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ứcHasForeignKey
. 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.

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.