Fluent API trong Entity Framework

Fluent API là gì?

Entity Framework Fluent API được sử dụng để cấu hình các lớp thực thể để ghi đè các quy ước mặc định của Entity Framework. 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 6, lớp DbModelBuilder hoạt động như một Fluent API, chúng ta có thể sử dụng nó để thực hiện các cấu hình nhiều thứ khác nhau. 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.

Để viết các cấu hình Fluent API, chúng ta cần ghi đè phương thức OnModelCreating() của lớp DbContext trong một lớp Context tùy chỉnh, như được trình bày bên dưới.


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

    }
}

Bạn có thể sử dụng các attribute chú thích dữ liệu và Fluent API cùng một lúc. Entity Framework ưu tiên Fluent API hơn các attribute chú thích dữ liệu.

Các phương thức quan trọng của Fluent API

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

  1. Cấu hình toàn mô hình: Cấu hình lược đồ mặc định, các thực thể được loại trừ trong ánh xạ, v.v.
  2. Cấu hình thực thể: Cấu hình thực thể cho ánh xạ bảng và mối quan hệ, ví dụ: khóa chính, 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, NotNull, nullable, khóa ngoại, kiểu dữ liệu, cột concurrency, v.v.

Bảng sau liệt kê các phương thức quan trọng của Fluent API:

Phương thức Mô tả
Cấu hình toàn mô hình
HasDefaultSchema Chỉ định lược đồ cơ sở dữ liệu mặc định.
ComplexType Cấu hình lớp là kiểu phức tạp.
Cấu hình 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 mối quan hệ một-nhiều hoặc nhiều-nhiều.
HasOptional Cấu hình một mối quan hệ tùy chọn sẽ tạo khóa ngoại không thể hủy trong cơ sở dữ liệu.
HasRequired Cấu hình mối quan hệ bắt buộc sẽ tạo cột khóa ngoại không thể rỗng trong cơ sở dữ liệu.
Ignore Cấu hình lớp 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.
Map Cho phép cấu hình nâng cao liên quan đến cách thực thể được ánh xạ tới lược đồ cơ sở dữ liệu.
MapToStoredProcedures Cấu hình kiểu thực thể để sử dụng các stored procedure INSERT, UPDATE và DELETE.
ToTable Cấu hình tên bảng cho thực thể.
Cấu hình thuộc tính
HasColumnAnnotation Đặt một chú thích trong mô hình cho cột cơ sở dữ liệu được sử dụng để lưu trữ thuộc tính.
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.
IsConcurrencyToken Cấu hình thuộc tính được sử dụng làm mã thông báo Concurrency.
IsOptional Cấu hình thuộc tính là tùy chọn sẽ tạo một cột nullable trong cơ sở dữ liệu.
HasParameterName Cấu hình tên của tham số được sử dụng trong stored procedure cho thuộc tính.
HasDatabaseGeneratedOption Cấu hình cách tạo giá trị cho cột tương ứng trong cơ sở dữ liệu, ví dụ tính toán, danh tính hoặc không có.
HasColumnOrder Cấu hình thứ tự của cột trong cơ sở dữ liệu được sử dụng để lưu trữ thuộc tính.
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.
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.
IsConcurrencyToken Cấu hình thuộc tính được sử dụng làm mã thông báo Concurrency.

Ánh xạ thực thể bằng Fluent API

Fluent API có thể được sử dụng để định cấu hình một thực thể để ánh xạ nó với bảng cơ sở dữ liệu, lược đồ mặc định, v.v.

Cấu hình lược đồ mặc định

Đầu tiên, hãy cấu hình một lược đồ mặc định cho các bảng trong cơ sở dữ liệu. Tuy nhiên, bạn có thể thay đổi lược đồ trong khi tạo các bảng riêng lẻ.

Ví dụ sau đặt lược đồ Admin làm lược đồ mặc định. Tất cả các đối tượng cơ sở dữ liệu sẽ được tạo trong lược đồ Quản trị trừ khi bạn chỉ định rõ ràng một lược đồ khác.


public class SchoolContext: DbContext 
{
    public SchoolDBContext(): base() 
    {
    }

    public DbSet<Student> Students { get; set; }
    public DbSet<Standard> Standards { get; set; }
        
    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        //Configure default schema
        modelBuilder.HasDefaultSchema("Admin");
    }
}

Ánh xạ thực thể vào bảng

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 Context, trong trường hợp này là StudentsStandards.

Bạn có thể ghi đè quy ước này và đặt tên bảng khác với các thuộc tính DbSet, như ví dụ ở bên dưới.


namespace CodeFirst_FluentAPI_Tutorials
{
    public class SchoolContext: DbContext 
    {
        public SchoolDBContext(): base() 
        {
        }

        public DbSet<Student> Students { get; set; }
        public DbSet<Standard> Standards { get; set; }
        
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            //Configure default schema
            modelBuilder.HasDefaultSchema("Admin");
                    
            //Map entity to table
            modelBuilder.Entity<Student>().ToTable("StudentInfo");
            modelBuilder.Entity<Standard>().ToTable("StandardInfo","dbo");
        }
    }
}

Như bạn có thể thấy trong ví dụ trên, chúng ta bắt đầu với phương thức Entity<TEntity>(). Hầu như bạn sẽ phải bắt đầu với phương thức Entity<TEntity>() để định cấu hình bằng Fluent API.

Chúng tôi đã sử dụng phương thức ToTable() để ánh xạ thực thể Student vào bảng StudentInfo và thực thể Standard vào bảng StandardInfo.

Lưu ý rằng bảng StudentInfo nằm trong lược đồ Admin và bảng StandardInfo nằm trong lược đồ dbo vì chúng tôi đã chỉ định lượng đồ mặc định là Admin và chỉ định lược đồ dbo cho bảng StandardInfo.

Ánh xạ thực thể vào bảng trong Fluent API

Ánh xạ thực thể vào nhiều bảng

Ví dụ sau đây minh họa cách ánh xạ thực thể Student vào nhiều bảng trong cơ sở dữ liệu.


namespace CodeFirst_FluentAPI_Tutorials
{
    public class SchoolContext: DbContext 
    {
        public SchoolDBContext(): base() 
        {
        }

        public DbSet<Student> Students { get; set; }
        public DbSet<Standard> Standards { get; set; }
        
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Student>().Map(m =>
            {
                m.Properties(p => new 
                { 
                    p.StudentId, 
                    p.StudentName
                });
                m.ToTable("StudentInfo");
            }).Map(m => 
            {
                m.Properties(p => new 
                { 
                    p.StudentId, 
                    p.Height, 
                    p.Weight, 
                    p.Photo, 
                    p.DateOfBirth
                });
                m.ToTable("StudentInfoDetail");
            });

            modelBuilder.Entity<Standard>().ToTable("StandardInfo");
        }
    }
}

Như bạn có thể thấy trong ví dụ trên, chúng tôi đã ánh xạ một số thuộc tính của thực thể Student vào bảng StudentInfo và các thuộc tính khác vào bảng StudentInfoDetail bằng phương thức Map().

Do đó, thực thể Student sẽ chia thành hai bảng, như hình dưới đây.

Ánh xạ thực thể vào nhiều bảng trong Fluent API

Phương thức Map() yêu cầu một tham số kiểu delegate. Bạn có thể truyền một delegate Action hoặc biểu thức lambda trong phương thức Map(), như ví dụ bên dưới.


using System.Data.Entity.ModelConfiguration.Configuration;

namespace CodeFirst_FluentAPI_Tutorials
{
    public class SchoolContext: DbContext 
    {
        public SchoolDBContext(): base() 
        {
        }

        public DbSet<Student> Students { get; set; }
        public DbSet<Standard> Standards { get; set; }
        
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Student>().Map(delegate(EntityMappingConfiguration<Student> studentConfig)
            {
                studentConfig.Properties(p => new 
                { 
                    p.StudentId, 
                    p.StudentName 
                });
                studentConfig.ToTable("StudentInfo");
            });

            Action<EntityMappingConfiguration<Student>> studentMapping = m =>
            {
                m.Properties(p => new 
                { 
                    p.StudentId, 
                    p.Height, 
                    p.Weight, 
                    p.Photo, 
                    p.DateOfBirth 
                });
                m.ToTable("StudentInfoDetail");
            };
            
            modelBuilder.Entity<Student>().Map(studentMapping);
            modelBuilder.Entity<Standard>().ToTable("StandardInfo");
        }
    }
}

Ánh xạ thuộc tính bằng Fluent API

Fluent API có thể được sử dụng để cấu hình các thuộc tính của thực thể để ánh xạ nó với cột db.

Sử dụng Fluent API, bạn có thể thay đổi tên cột, kiểu dữ liệu, kích thước, Null hoặc NotNull, PrimaryKey, ForeignKey, cột concurrency, v.v.

Chúng tôi sẽ cấu hình các lớp thực thể sau.


public class Student
{
    public int StudentKey { get; set; }
    public string StudentName { get; set; }
    public DateTime DateOfBirth { get; set; }
    public byte[]  Photo { get; set; }
    public decimal Height { get; set; }
    public float Weight { get; set; }
        
    public Standard Standard { get; set; }
}
    
public class Standard
{
    public int StandardKey { get; set; }
    public string StandardName { get; set; }
    
    public ICollection<Student> Students { get; set; }
}

Cấu hình khóa chính và khóa chính tổng hợp

Các lớp thực thể của chúng tôi ở trên, không tuân theo quy ước Code First cho khóa chính vì chúng không có thuộc tính Id hoặc {Tên lớp} + "Id".

Vì vậy, bạn có thể cấu hình thuộc tính khóa bằng phương thức HasKey() như dưới đây. Hãy nhớ rằng modelBuilder.Entity<TEntity>() trả về đối tượng EntityTypeConfiguration.


public class SchoolContext: DbContext 
{
    public SchoolDBContext(): base() 
    {
    }

    public DbSet<Student> Students { get; set; }
    public DbSet<Standard> Standards { get; set; }
        
    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        //Configure primary key
        modelBuilder.Entity<Student>().HasKey<int>(s => s.StudentKey);
        modelBuilder.Entity<Standard>().HasKey<int>(s => s.StandardKey);

        //Configure composite primary key
        modelBuilder.Entity<Student>().HasKey<int>(s => new 
        { 
            s.StudentKey, 
            s.StudentName 
        }); 
    }
}

Cấu hình tên cột, kiểu dữ liệu và thứ tự

Quy ước Code First mặc định tạo một cột cho một thuộc tính có cùng tên, thứ tự và kiểu dữ liệu. Bạn có thể ghi đè quy ước này, như ví dụ bên dưới.


public class SchoolContext: DbContext 
{
    public SchoolDBContext(): base() 
    {
    }

    public DbSet<Student> Students { get; set; }
    public DbSet<Standard> Standards { get; set; }
        
    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        //Configure Column
        modelBuilder.Entity<Student>()
                    .Property(p => p.DateOfBirth)
                    .HasColumnName("DoB")
                    .HasColumnOrder(3)
                    .HasColumnType("datetime2");
    }
}

Như bạn có thể thấy trong ví dụ trên, phương thức Property() được sử dụng để cấu hình một thuộc tính của một thực thể.

Phương thức HasColumnName() được sử dụng để thay đổi tên cột của thuộc tính DateOfBirth. Ngoài ra, các phương thức HasColumnOrder()HasColumnType() thay đổi thứ tự và kiểu dữ liệu của cột tương ứng.

Phương thức  modelBuilder.Entity<TEntity>() .Property(expression) cho phép bạn sử dụng các phương thức khác nhau để định cấu hình một thuộc tính cụ thể, như hình ảnh bên dưới.

Cấu hình tên cột, kiểu dữ liệu và thứ tự bằng Fluent API

Cấu hình cột Null hoặc NotNull

EF 6 API sẽ tạo cột NotNull cho thuộc tính kiểu dữ liệu nguyên thủy vì kiểu dữ liệu nguyên thủy không thể Null trừ khi được đánh dấu là nullable bằng cách sử dụng dấu ? hoặc kiểu Nullable<T>.

Sử dụng phương thức IsOptional() để tạo một cột nullable cho một thuộc tính. Theo cách tương tự, sử dụng phương thức IsRequired() để tạo cột NotNull.


public class SchoolContext: DbContext 
{
    public SchoolDBContext(): base() 
    {
    }

    public DbSet<Student> Students { get; set; }
    public DbSet<Standard> Standards { get; set; }
        
    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
            //Configure Null Column
        modelBuilder.Entity<Student>()
                .Property(p => p.Heigth)
                .IsOptional();
                        
            //Configure NotNull Column
            modelBuilder.Entity<Student>()
                .Property(p => p.Weight)
                .IsRequired();
    }
}

Cấu hình kích thước cột

Code First sẽ đặt kích thước tối đa của kiểu dữ liệu cho một cột. Bạn có thể ghi đè quy ước này, như ví dụ bên dưới.


public class SchoolContext: DbContext 
{
    public SchoolDBContext(): base() 
    {
    }

    public DbSet<Student> Students { get; set; }
    public DbSet<Standard> Standards { get; set; }
        
    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        //Set StudentName column size to 50
        modelBuilder.Entity<Student>()
                .Property(p => p.StudentName)
                .HasMaxLength(50);
                        
        //Set StudentName column size to 50 and change datatype to nchar 
        //IsFixedLength() change datatype from nvarchar to nchar
        modelBuilder.Entity<Student>()
                .Property(p => p.StudentName)
                .HasMaxLength(50).IsFixedLength();
                        
        //Set size decimal(2,2)
            modelBuilder.Entity<Student>()
                .Property(p => p.Height)
                .HasPrecision(2, 2);
    }
}

Như bạn có thể thấy trong ví dụ trên, chúng tôi đã sử dụng phương thức  HasMaxLength() để thiết lập kích thước của một cột.

Phương thức IsFixedLength() chuyển kiểu nvarchar thành kiểu nchar. Theo cách tương tự, phương thức HasPrecision() cũng thay đổi độ chính xác của cột decimal.

Cấu hình cột concurrency

Bạn có thể cấu hình một thuộc tính dưới dạng cột concurrency (chống xung đột) bằng phương thức ConcurrencyToken(), như được trình bày bên dưới.


public class SchoolContext: DbContext 
{
    public SchoolDBContext(): base() 
    {
    }

    public DbSet<Student> Students { get; set; }
    public DbSet<Standard> Standards { get; set; }
        
    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        //Set StudentName as concurrency column
        modelBuilder.Entity<Student>()
                .Property(p => p.StudentName)
                .IsConcurrencyToken();
    }
}

Như bạn đã thấy ở trên, chúng tôi thiết lập cột StudentName là cột concurrency để nó sẽ được thêm trong mệnh đề where trong các lệnh UPDATE và DELETE.

Bạn cũng có thể sử dụng phương thức IsRowVersion() cho thuộc tính kiểu byte[] để biến nó thành một cột concurrency.



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.