Khung thực thể - API thông thạo
Fluent API là một cách nâng cao để chỉ định cấu hình mô hình bao gồm mọi thứ mà chú thích dữ liệu có thể thực hiện ngoài một số cấu hình nâng cao hơn không thể thực hiện với chú thích dữ liệu. Chú thích dữ liệu và API thông thạo có thể được sử dụng cùng nhau, nhưng Code First ưu tiên cho API thông thạo> chú thích dữ liệu> quy ước mặc định.
API thông thạo là một cách khác để định cấu hình các lớp miền của bạn.
Code First Fluent API thường được truy cập nhiều nhất bằng cách ghi đè phương thức OnModelCreating trên DbContext dẫn xuất của bạn.
API Fluent cung cấp nhiều chức năng cho cấu hình hơn DataAnnotations. API Fluent hỗ trợ các loại ánh xạ sau.
Trong chương này, chúng ta sẽ tiếp tục với ví dụ đơn giản chứa các lớp Sinh viên, Khóa học và Ghi danh và một lớp ngữ cảnh có tên MyContext như được hiển thị trong đoạn mã sau.
using System.Data.Entity;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EFCodeFirstDemo {
class Program {
static void Main(string[] args) {}
}
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; }
public Grade? Grade { get; set; }
public virtual Course Course { get; set; }
public virtual Student Student { get; set; }
}
public class Student {
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
public class Course {
public int CourseID { get; set; }
public string Title { get; set; }
public int Credits { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
public class MyContext : DbContext {
public virtual DbSet<Course> Courses { get; set; }
public virtual DbSet<Enrollment> Enrollments { get; set; }
public virtual DbSet<Student> Students { get; set; }
}
}
Để truy cập API Fluent, bạn cần ghi đè phương thức OnModelCreating trong DbContext. Hãy xem một ví dụ đơn giản trong đó chúng ta sẽ đổi tên cột trong bảng sinh viên từ FirstMidName thành FirstName như được hiển thị trong đoạn mã sau.
public class MyContext : DbContext {
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
modelBuilder.Entity<Student>().Property(s ⇒ s.FirstMidName)
.HasColumnName("FirstName");}
public virtual DbSet<Course> Courses { get; set; }
public virtual DbSet<Enrollment> Enrollments { get; set; }
public virtual DbSet<Student> Students { get; set; }
}
DbModelBuilder được sử dụng để ánh xạ các lớp CLR vào một lược đồ cơ sở dữ liệu. Nó là lớp chính và trên đó bạn có thể cấu hình tất cả các lớp miền của mình. Cách tiếp cận tập trung vào mã này để xây dựng Mô hình dữ liệu thực thể (EDM) được gọi là Mã đầu tiên.
Fluent API cung cấp một số phương pháp quan trọng để định cấu hình các thực thể và thuộc tính của nó để ghi đè các quy ước Code First khác nhau. Dưới đây là một số trong số chúng.
Sr không. | Tên & Mô tả phương pháp |
---|---|
1 | ComplexType<TComplexType> Đăng ký một kiểu như một kiểu phức tạp trong mô hình và trả về một đối tượng có thể được sử dụng để cấu hình kiểu phức tạp. Phương thức này có thể được gọi nhiều lần cho cùng một kiểu để thực hiện nhiều dòng cấu hình. |
2 | Entity<TEntityType> Đăng ký một loại thực thể như một phần của mô hình và trả về một đối tượng có thể được sử dụng để định cấu hình thực thể. Phương thức này có thể được gọi nhiều lần cho cùng một thực thể để thực hiện nhiều dòng cấu hình. |
3 | HasKey<TKey> Định cấu hình (các) thuộc tính khóa chính cho loại thực thể này. |
4 | HasMany<TTargetEntity> Định cấu hình nhiều mối quan hệ từ loại thực thể này. |
5 | HasOptional<TTargetEntity> Định cấu hình mối quan hệ tùy chọn từ loại thực thể này. Các phiên bản của loại thực thể sẽ có thể được lưu vào cơ sở dữ liệu mà không cần chỉ định mối quan hệ này. Khoá ngoại trong cơ sở dữ liệu sẽ không có giá trị. |
6 | HasRequired<TTargetEntity> Định cấu hình mối quan hệ bắt buộc từ loại thực thể này. Các phiên bản của loại thực thể sẽ không thể được lưu vào cơ sở dữ liệu trừ khi mối quan hệ này được chỉ định. Khoá ngoại trong cơ sở dữ liệu sẽ không thể null. |
7 | Ignore<TProperty> Loại trừ một thuộc tính khỏi mô hình để nó không được ánh xạ tới cơ sở dữ liệu. (Được kế thừa từ StructuralTypeConfiguration <TStructuralType>) |
số 8 | Property<T> Định cấu hình thuộc tính struct được xác định trên loại này. (Được kế thừa từ StructuralTypeConfiguration <TStructuralType>) |
9 | ToTable(String) Định cấu hình tên bảng mà loại thực thể này được ánh xạ tới. |
Fluent API cho phép bạn định cấu hình các thực thể của mình hoặc thuộc tính của chúng, cho dù bạn muốn thay đổi điều gì đó về cách chúng ánh xạ với cơ sở dữ liệu hoặc cách chúng liên quan với nhau. Có rất nhiều ánh xạ và mô hình hóa mà bạn có thể tác động bằng cách sử dụng các cấu hình. Sau đây là các loại ánh xạ chính mà API Fluent hỗ trợ:
- Bản đồ thực thể
- Bản đồ thuộc tính
Bản đồ thực thể
Ánh xạ thực thể chỉ là một số ánh xạ đơn giản sẽ tác động đến sự hiểu biết của Entity Framework về cách các lớp được ánh xạ tới cơ sở dữ liệu. Tất cả những điều này chúng ta đã thảo luận trong phần chú thích dữ liệu và ở đây chúng ta sẽ xem cách đạt được những điều tương tự bằng cách sử dụng Fluent API.
Vì vậy, thay vì đi vào các lớp miền để thêm các cấu hình này, chúng ta có thể thực hiện việc này bên trong ngữ cảnh.
Điều đầu tiên là ghi đè phương thức OnModelCreating, phương thức này cho phép modelBuilder hoạt động.
Lược đồ mặc định
Lược đồ mặc định là dbo khi cơ sở dữ liệu được tạo. Bạn có thể sử dụng phương thức HasDefaultSchema trên DbModelBuilder để chỉ định lược đồ cơ sở dữ liệu để sử dụng cho tất cả các bảng, thủ tục được lưu trữ, v.v.
Hãy xem ví dụ sau, trong đó lược đồ quản trị được áp dụng.
public class MyContext : DbContext {
public MyContext() : base("name = MyContextDB") {}
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
//Configure default schema
modelBuilder.HasDefaultSchema("Admin");
}
public virtual DbSet<Course> Courses { get; set; }
public virtual DbSet<Enrollment> Enrollments { get; set; }
public virtual DbSet<Student> Students { get; set; }
}
Ánh xạ thực thể vào bảng
Với quy ước mặc định, Code First sẽ tạo các bảng cơ sở dữ liệu với tên của các thuộc tính DbSet trong lớp ngữ cảnh như Khóa học, Đăng ký và Sinh viên. Nhưng nếu bạn muốn các tên bảng khác nhau thì bạn có thể ghi đè quy ước này và có thể cung cấp tên bảng khác với thuộc tính DbSet, như được hiển thị trong đoạn mã sau.
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
//Configure default schema
modelBuilder.HasDefaultSchema("Admin");
//Map entity to table
modelBuilder.Entity<Student>().ToTable("StudentData");
modelBuilder.Entity<Course>().ToTable("CourseDetail");
modelBuilder.Entity<Enrollment>().ToTable("EnrollmentInfo");
}
Khi cơ sở dữ liệu được tạo, bạn sẽ thấy tên bảng như được chỉ định trong phương thức OnModelCreating.
Tách đối tượng (Đối tượng ánh xạ thành nhiều bảng)
Thực thể Tách cho phép bạn kết hợp dữ liệu đến từ nhiều bảng thành một lớp duy nhất và nó chỉ có thể được sử dụng với các bảng có mối quan hệ một-một giữa chúng. Hãy xem ví dụ sau, trong đó thông tin Sinh viên được ánh xạ thành hai bảng.
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
//Configure default schema
modelBuilder.HasDefaultSchema("Admin");
//Map entity to table
modelBuilder.Entity<Student>().Map(sd ⇒ {
sd.Properties(p ⇒ new { p.ID, p.FirstMidName, p.LastName });
sd.ToTable("StudentData");
})
.Map(si ⇒ {
si.Properties(p ⇒ new { p.ID, p.EnrollmentDate });
si.ToTable("StudentEnrollmentInfo");
});
modelBuilder.Entity<Course>().ToTable("CourseDetail");
modelBuilder.Entity<Enrollment>().ToTable("EnrollmentInfo");
}
Trong đoạn mã trên, bạn có thể thấy rằng thực thể Student được chia thành hai bảng sau bằng cách ánh xạ một số thuộc tính vào bảng StudentData và một số thuộc tính vào bảng StudentEnrollmentInfo bằng phương pháp Map.
StudentData - Chứa FirstMidName và Last Name của học sinh.
StudentEnrollmentInfo - Chứa Ngày đăng ký.
Khi cơ sở dữ liệu được tạo, bạn sẽ thấy các bảng sau trong cơ sở dữ liệu của mình như được hiển thị trong hình ảnh sau.
Bản đồ thuộc tính
Phương thức Thuộc tính được sử dụng để định cấu hình các thuộc tính cho từng thuộc tính thuộc thực thể hoặc kiểu phức hợp. Phương thức Thuộc tính được sử dụng để lấy một đối tượng cấu hình cho một thuộc tính nhất định. Bạn cũng có thể ánh xạ và định cấu hình các thuộc tính của các lớp miền của mình bằng API Fluent.
Định cấu hình khóa chính
Quy ước mặc định cho các khóa chính là:
- Lớp xác định thuộc tính có tên là “ID” hoặc “Id”
- Tên lớp theo sau là “ID” hoặc “Id”
Nếu lớp của bạn không tuân theo các quy ước mặc định cho khóa chính như được hiển thị trong mã sau của lớp Sinh viên -
public class Student {
public int StdntID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
Sau đó, để đặt một cách rõ ràng thuộc tính làm khóa chính, bạn có thể sử dụng phương thức HasKey như được hiển thị trong đoạn mã sau:
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
//Configure default schema
modelBuilder.HasDefaultSchema("Admin");
// Configure Primary Key
modelBuilder.Entity<Student>().HasKey<int>(s ⇒ s.StdntID);
}
Định cấu hình cột
Trong Entity Framework, theo mặc định Code First sẽ tạo một cột cho thuộc tính có cùng tên, thứ tự và kiểu dữ liệu. Nhưng bạn cũng có thể ghi đè quy ước này, như được hiển thị trong đoạn mã sau.
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
//Configure default schema
modelBuilder.HasDefaultSchema("Admin");
//Configure EnrollmentDate Column
modelBuilder.Entity<Student>().Property(p ⇒ p.EnrollmentDate)
.HasColumnName("EnDate")
.HasColumnType("DateTime")
.HasColumnOrder(2);
}
Định cấu hình thuộc tính MaxLength
Trong ví dụ sau, thuộc tính Tiêu đề khóa học không được dài hơn 24 ký tự. Khi người dùng chỉ định giá trị dài hơn 24 ký tự, thì người dùng sẽ nhận được ngoại lệ DbEntityValidationException.
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
//Configure default schema
modelBuilder.HasDefaultSchema("Admin");
modelBuilder.Entity<Course>().Property(p ⇒ p.Title).HasMaxLength(24);
}
Định cấu hình thuộc tính Null hoặc NotNull
Trong ví dụ sau, thuộc tính Tiêu đề khóa học là bắt buộc nên phương thức IsRequired được sử dụng để tạo cột NotNull. Tương tự, Ngày đăng ký học sinh là tùy chọn, vì vậy chúng tôi sẽ sử dụng phương pháp IsOptional để cho phép giá trị null trong cột này như được hiển thị trong đoạn mã sau.
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
//Configure default schema
modelBuilder.HasDefaultSchema("Admin");
modelBuilder.Entity<Course>().Property(p ⇒ p.Title).IsRequired();
modelBuilder.Entity<Student>().Property(p ⇒ p.EnrollmentDate).IsOptional();
//modelBuilder.Entity<Student>().Property(s ⇒ s.FirstMidName)
//.HasColumnName("FirstName");
}
Định cấu hình mối quan hệ
Mối quan hệ, trong ngữ cảnh cơ sở dữ liệu, là một tình huống tồn tại giữa hai bảng cơ sở dữ liệu quan hệ, khi một bảng có khóa ngoại tham chiếu đến khóa chính của bảng kia. Khi làm việc với Code First, bạn xác định mô hình của mình bằng cách xác định các lớp CLR miền của bạn. Theo mặc định, Entity Framework sử dụng quy ước Code First để ánh xạ các lớp của bạn với lược đồ cơ sở dữ liệu.
Nếu bạn sử dụng quy ước đặt tên Code First, trong hầu hết các trường hợp, bạn có thể dựa vào Code First để thiết lập mối quan hệ giữa các bảng của mình dựa trên các khóa ngoại và thuộc tính điều hướng.
Nếu chúng không đáp ứng các quy ước đó, cũng có những cấu hình bạn có thể sử dụng để tác động đến mối quan hệ giữa các lớp và cách các mối quan hệ đó được thực hiện trong cơ sở dữ liệu khi bạn thêm cấu hình trong Code First.
Một số trong số đó có sẵn trong chú thích dữ liệu và bạn có thể áp dụng một số cách phức tạp hơn với API Fluent.
Định cấu hình mối quan hệ một-một
Khi bạn xác định mối quan hệ một-một trong mô hình của mình, bạn sử dụng thuộc tính điều hướng tham chiếu trong mỗi lớp. Trong cơ sở dữ liệu, cả hai bảng chỉ có thể có một bản ghi ở hai bên của mối quan hệ. Mỗi giá trị khóa chính chỉ liên quan đến một bản ghi (hoặc không có bản ghi) trong bảng liên quan.
Mối quan hệ một-một được tạo nếu cả hai cột liên quan đều là khóa chính hoặc có các ràng buộc duy nhất.
Trong mối quan hệ một-một, khóa chính hoạt động bổ sung như một khóa ngoại và không có cột khóa ngoại riêng biệt cho cả hai bảng.
Kiểu quan hệ này không phổ biến vì hầu hết thông tin liên quan theo cách này đều nằm trong một bảng.
Hãy xem ví dụ sau, nơi chúng ta sẽ thêm một lớp khác vào mô hình của mình để tạo mối quan hệ một-một.
public class Student {
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
public virtual StudentLogIn StudentLogIn { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
public class StudentLogIn {
[Key, ForeignKey("Student")]
public int ID { get; set; }
public string EmailID { get; set; }
public string Password { get; set; }
public virtual Student Student { get; set; }
}
Như bạn có thể thấy trong đoạn mã trên, các thuộc tính Key và ForeignKey được sử dụng cho thuộc tính ID trong lớp StudentLogIn, để đánh dấu nó là Khóa chính cũng như Khóa ngoại.
Để định cấu hình mối quan hệ một-không hoặc một giữa Student và StudentLogIn bằng cách sử dụng Fluent API, bạn cần ghi đè phương thức OnModelCreating như được hiển thị trong đoạn mã sau.
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
//Configure default schema
modelBuilder.HasDefaultSchema("Admin");
// Configure ID as PK for StudentLogIn
modelBuilder.Entity<StudentLogIn>()
.HasKey(s ⇒ s.ID);
// Configure ID as FK for StudentLogIn
modelBuilder.Entity<Student>()
.HasOptional(s ⇒ s.StudentLogIn) //StudentLogIn is optional
.WithRequired(t ⇒ t.Student); // Create inverse relationship
}
Trong hầu hết các trường hợp, Khung thực thể có thể suy ra loại nào là loại phụ thuộc và loại nào là chính trong mối quan hệ. Tuy nhiên, khi cả hai đầu mối quan hệ là bắt buộc hoặc cả hai bên đều không bắt buộc thì Khung thực thể không thể xác định người phụ thuộc và người chính. Khi cả hai kết thúc mối quan hệ đều được yêu cầu, bạn có thể sử dụng HasRequired như được hiển thị trong đoạn mã sau.
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
//Configure default schema
modelBuilder.HasDefaultSchema("Admin");
// Configure ID as PK for StudentLogIn
modelBuilder.Entity<StudentLogIn>()
.HasKey(s ⇒ s.ID);
// Configure ID as FK for StudentLogIn
modelBuilder.Entity<Student>()
.HasRequired(r ⇒ r.Student)
.WithOptional(s ⇒ s.StudentLogIn);
}
Khi cơ sở dữ liệu được tạo, bạn sẽ thấy mối quan hệ đó được tạo như thể hiện trong hình sau.
Định cấu hình mối quan hệ một-nhiều
Bảng khóa chính chỉ chứa một bản ghi liên quan đến không, một hoặc nhiều bản ghi trong bảng liên quan. Đây là kiểu quan hệ được sử dụng phổ biến nhất.
Trong kiểu quan hệ này, một hàng trong bảng A có thể có nhiều hàng phù hợp trong bảng B, nhưng một hàng trong bảng B chỉ có thể có một hàng phù hợp trong bảng A.
Khóa ngoại được xác định trên bảng đại diện cho nhiều phần cuối của mối quan hệ.
Ví dụ, trong sơ đồ trên, bảng Student và Enrollment có mối quan hệ một bên, mỗi học sinh có thể có nhiều đăng ký, nhưng mỗi ghi danh chỉ thuộc về một học sinh.
Dưới đây là Sinh viên và Đăng ký có mối quan hệ một-nhiều, nhưng khóa ngoại trong bảng Đăng ký không tuân theo các quy ước mặc định về Mã đầu tiên.
public class Enrollment {
public int EnrollmentID { get; set; }
public int CourseID { get; set; }
//StdntID is not following code first conventions name
public int StdntID { get; set; }
public Grade? Grade { get; set; }
public virtual Course Course { get; set; }
public virtual Student Student { get; set; }
}
public class Student {
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
public virtual StudentLogIn StudentLogIn { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
Trong trường hợp này, để định cấu hình mối quan hệ một-nhiều bằng Fluent API, bạn cần sử dụng phương thức HasForeignKey như được hiển thị trong đoạn mã sau.
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
//Configure default schema
modelBuilder.HasDefaultSchema("Admin");
//Configure FK for one-to-many relationship
modelBuilder.Entity<Enrollment>()
.HasRequired<Student>(s ⇒ s.Student)
.WithMany(t ⇒ t.Enrollments)
.HasForeignKey(u ⇒ u.StdntID);
}
Khi cơ sở dữ liệu được tạo, bạn sẽ thấy rằng mối quan hệ được tạo như thể hiện trong hình sau.
Trong ví dụ trên, phương thức HasRequired chỉ định rằng thuộc tính điều hướng Sinh viên phải là Null. Vì vậy, bạn phải chỉ định thực thể Student with Enrollment mỗi khi bạn thêm hoặc cập nhật Enrollment. Để xử lý điều này, chúng ta cần sử dụng phương thức HasOptional thay vì phương thức HasRequired.
Định cấu hình mối quan hệ nhiều-nhiều
Mỗi bản ghi trong cả hai bảng có thể liên quan đến bất kỳ số lượng bản ghi nào (hoặc không có bản ghi) trong bảng khác.
Bạn có thể tạo mối quan hệ như vậy bằng cách xác định bảng thứ ba, được gọi là bảng nối, có khóa chính bao gồm các khóa ngoại từ cả bảng A và bảng B.
Ví dụ, bảng Sinh viên và bảng Khóa học có mối quan hệ nhiều-nhiều.
Sau đây là các lớp Sinh viên và Khóa học trong đó Sinh viên và Khóa học có mối quan hệ nhiều bên, vì cả hai lớp đều có thuộc tính điều hướng Sinh viên và Khóa học là tập hợp. Nói cách khác, một thực thể có một tập hợp thực thể khác.
public class Student {
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
public virtual ICollection<Course> Courses { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
public class Course {
public int CourseID { get; set; }
public string Title { get; set; }
public int Credits { get; set; }
public virtual ICollection<Student> Students { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
Để định cấu hình mối quan hệ nhiều-nhiều giữa Sinh viên và Khóa học, bạn có thể sử dụng API Fluent như được hiển thị trong đoạn mã sau.
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
//Configure default schema
modelBuilder.HasDefaultSchema("Admin");
// Configure many-to-many relationship
modelBuilder.Entity<Student>()
.HasMany(s ⇒ s.Courses)
.WithMany(s ⇒ s.Students);
}
Quy ước Code First mặc định được sử dụng để tạo một bảng nối khi cơ sở dữ liệu được tạo. Kết quả là bảng StudentCourses được tạo với các cột Course_CourseID và Student_ID như được hiển thị trong hình sau.
Nếu bạn muốn chỉ định tên bảng nối và tên của các cột trong bảng, bạn cần thực hiện cấu hình bổ sung bằng cách sử dụng phương pháp Bản đồ.
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
//Configure default schema
modelBuilder.HasDefaultSchema("Admin");
// Configure many-to-many relationship
modelBuilder.Entity<Student>()
.HasMany(s ⇒ s.Courses)
.WithMany(s ⇒ s.Students)
.Map(m ⇒ {
m.ToTable("StudentCoursesTable");
m.MapLeftKey("StudentID");
m.MapRightKey("CourseID");
});
}
Bạn có thể thấy khi cơ sở dữ liệu được tạo, tên bảng và cột được tạo như được chỉ định trong đoạn mã trên.
Chúng tôi khuyên bạn nên thực hiện ví dụ trên theo cách từng bước để hiểu rõ hơn.