Fluent API trong code first: chỉ định cấu trúc cơ sở dữ liệu

    1

    Fluent API là cách chỉ định cấu trúc cơ sở dữ liệu thứ hai trong Entity Framework Code first. Cách cấu hình này sử dụng class DbModelBuilder với chuỗi phương thức (method chaining) ghép nối với nhau. Đây là cách thức cấu hình được sử dụng phổ biến và có nhiều ưu điểm so với sử dụng data annotation attribute đã biết ở bài trước.

    Mô hình ghép nối lời gọi các phương thức thành chuỗi cũng được gọi là Fluent API pattern.

    Bài học này sẽ giúp bạn làm quen với kỹ thuật sử dụng fluent api với lớp DbModelBuilder để chỉ định cấu trúc bảng dữ liệu tương ứng cho lớp entity. Bạn cũng sẽ học cách sử dụng lớp cấu hình riêng (lớp EntityTypeConfiguration) cho từng lớp entity.

    [signinlocker id=”16252″]

    Fluent API, lớp DbModelBuilder

    Sử dụng fluent API với lớp DbModelBuilder rất đơn giản. Trong lớp Context (kế thừa DbContext) bạn chỉ cần ghi đè (override) phương thức OnModelCreating và chỉ định cấu hình cho từng property của lớp entity thông qua một object của lớp DbModelBuilder.

    Sử dụng fluent api trong C#

    Hãy cùng thực hiện một ví dụ để minh họa cho cách sử dụng lớp DbModelBuilder và phương thức OnModelCreating.

    Trước hết thêm project mới P02_FluentApi vào solution S04_Configuration (đã tạo từ bài trước) và cài đặt Entity Framework cho project này:

    • cài đặt thư viện Entity Framework từ NuGet;
    • bổ sung connectionstring vào App.Config:
    <connectionStrings>
        <add name="P02_FluentApi" providerName="System.Data.SqlClient" connectionString="Data Source=.; Initial Catalog=S04_P02_FluentApi; Integrated Security=true"/>
    </connectionStrings>
    • Tạo lớp Person trong file Person.cs với nội dung như sau:
    namespace P02_FluentApi
    {
        class Person
        {
            public int PersonId { get; set; }
            public string FirstName { get; set; }
            public string LastName { get; set; }
            public string MiddleName { get; set; }
        }
    }
    • Tạo lớp Context trong file Context.cs với nội dung như sau:
    using System.Data.Entity;
    namespace P02_FluentApi
    {
        class Context : DbContext
        {
            public Context() : base("name=P02_FluentApi")
            {
            }
            public DbSet<Person> People { get; set; }
            protected override void OnModelCreating(DbModelBuilder modelBuilder)
            {
                modelBuilder.Entity<Person>().Property(p => p.FirstName).HasMaxLength(30);
                modelBuilder.Entity<Person>().Property(p => p.LastName).HasMaxLength(30);
                modelBuilder.Entity<Person>().Property(p => p.MiddleName)
                    .HasMaxLength(1)
                    .IsFixedLength()
                    .IsUnicode(false);
            }
        }
    }
    • Viết code cho phương thức Main như sau:
    using System;
    namespace P02_FluentApi
    {
        class Program
        {
            static void Main(string[] args)
            {
                using (var context = new Context())
                {
                    var success = context.Database.CreateIfNotExists();
                    if (success)
                    {
                        Console.WriteLine("New database created!");
                    }
                    else
                    {
                        Console.WriteLine("Database exists or creation failed");
                    }
                }
                Console.ReadKey();
            }
        }
    }

    Dịch và chạy chương trình. Ở lần chạy đầu tiên, chương trình sẽ tự tạo cơ sở dữ liệu mới (có tên S04_P02_FluentApi).

    Lớp DbModelBuilder

    Trong ví dụ trên bạn có thể thấy, lớp entity Person giờ không có các attribute rối mắt nữa. Toàn bộ việc cấu hình bảng dữ liệu được đưa về lớp Context thông qua ghi đè phương thức OnModelCreating của lớp cha DbContext:

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Person>().Property(p => p.FirstName).HasMaxLength(30);
        modelBuilder.Entity<Person>().Property(p => p.LastName).HasMaxLength(30);
        modelBuilder.Entity<Person>().Property(p => p.MiddleName)
            .HasMaxLength(1)
            .IsFixedLength()
            .IsUnicode(false);
    }

    Phương thức này sẽ tự động được gọi khi Entity Framework sinh ra bảng dữ liệu. Trong thân phương thức này, bạn sử dụng biến modelBuilder của lớp DbModelBuilder để chỉ định cấu hình cho từng property của lớp entity Person.

    Cách sử dụng class này rất đơn giản:

    • Bạn chỉ định lớp entity cần cấu hình thông qua phương thức generic Entity<T>(). Bạn cung cấp tên lớp entity vào vị trí của T: Entity<Person>()
    • Sau khi đã chỉ định lớp entity, bạn chỉ định tiếp property cần cấu hình qua phương thức Property(). Phương thức này nhận một hàm lambda làm tham số. Hàm lambda này chỉ đơn giản là trả lại property đang cần cấu hình:
      Property(p => p.FirstName)
      Property(p => p.LastName)
      Property(p => p.MiddleName)

      Phương thức Property sẽ tự động lấy được các thông tin khác của property này.
    • Sau khi chỉ định property cần cấu hình, bạn tiếp tục chỉ định các cấu hình cụ thể thông qua các phương thức tương ứng. Hãy để ý rằng, bạn có thể ghép các phương thức cấu hình này thành chuỗi mà không cần gọi riêng rẽ từng cái một. Cách viết này chính là mô hình fluent api được xây dựng trong lớp DbModelBuilder.

    Trong ví dụ trên, chúng ta cấu hình bảng dữ liệu theo đúng yêu cầu từ bài trước:

    • FirstName và LastName có không quá 30 ký tự: HasMaxLength(30);
    • MiddleName có đúng 1 ký tự (HasMaxLength(1) và IsFixedLength()), không dùng ký tự unicode (IsUnicode(false)).

    Các phương thức của fluent api

    Dưới đây là danh sách các phương thức của Fluent Api. Chúng ta sẽ không đi sâu vào từng phương thức. Bạn đọc có thể dễ dàng tìm hiểu cách sử dụng của mỗi phương thức thông qua Intellisense của Visual studio.

    Các phương thức tác dụng trên lớp entity

    HasIndex()Chỉ định trường index
    HasKey()Chỉ định trường khóa chính
    HasMany()Chỉ định quan hệ 1-n hoặc n-n
    HasOptional()Chỉ định quan hệ 1-0..1
    HasRequired()Chỉ định quan hệ 1-1
    Ignore()Chỉ định class không được ánh xạ thành bảng dữ liệu
    Map()Chỉ định các cấu hình nâng cao
    MapToStoredProcedures()Cấu hình để sử dụng INSERT, UPDATE and DELETE stored procedures
    ToTable()Chỉ định tên bảng dữ liệu tương ứng

    Các phương thức chỉ định quan hệ giữa các bảng sẽ được xem xét chi tiết trong loạt bài học sau.

    Các phương thức tác dụng trên property

    HasColumnAnnotation()
    IsRequired()Chỉ định trường bắt buộc khi gọi SaveChanges()
    IsConcurrencyToken()Configures the property to be used as an optimistic concurrency token
    IsOptional()Trường tương ứng có thể nhận giá trị null
    HasParameterName()Tên tham số sử dụng trong stored procedure tương ứng của property
    HasDatabaseGeneratedOption()Chỉ định cách sinh dữ liệu tự động của cột trong cơ sở dữ liệu (computed, identity, none)
    HasColumnOrder()Chỉ định thứ tự của cột tương ứng trong csdl
    HasColumnType()Configures the data type of the corresponding column of a property in the database.
    HasColumnName()Configures the corresponding column name of a property in the database.
    IsConcurrencyToken()Configures the property to be used as an optimistic concurrency token.

    Các phương thức này phụ thuộc vào kiểu của property đang được cấu hình. Bạn sẽ biết thêm về chúng trong phần cuối của bài học này.

    Cũng giống như khi học về attribute, chúng ta không trình bày chi tiết cách sử dụng của từng phương thức trên. Bạn sẽ gặp chúng khi học các chủ đề riêng. Vì vậy, không cần cố học thuộc hoặc ghi nhớ các phương thức đó. Bài học này chỉ hướng tới cung cấp cho bạn phương pháp chung nhất về sử dụng fluent API.

    Lớp hỗ trợ cấu hình EntityTypeConfiguration<T>

    Phương pháp chúng ta sử dụng ở phần trên vốn rất tiện lợi khi nó tập trung code cấu hình vào một nơi duy nhất. Bạn không cần làm rối code của lớp entity với các attribute. Tuy nhiên, phương pháp này sẽ phát sinh vấn đề nếu số lượng entity class tăng lên, hoặc số lượng property của mỗi class khá lớn. Tập trung toàn bộ code cấu hình vào một phương thức duy nhất dẫn đến rất khó bảo trì về sau.

    Fluent Api có thể được sử dụng theo một cách khác: vận dụng lớp cấu hình EntityTypeConfiguration<T>. Cách thức này giải quyết được vấn đề vừa nêu ở trên.

    Ý tưởng của giải pháp này là tạo ra cho mỗi entity class một lớp cấu hình riêng, thường gọi là lớp buddy. Trong lớp buddy bạn áp dụng đúng các phương thức của fluent api như đã biết. Trong phương thức OnModelCreating giờ không cần chỉ định code cấu hình cụ thể nữa mà chỉ cần tạo object của mỗi lớp buddy và thêm vào property Configurations của DbModelBuilder.

    Để dễ hình dung, hãy cùng thực hiện lại ví dụ trên theo cách mới. Bạn có thể thực hiện luôn trên project P02_FluentApi bên trên hoặc tạo thêm project P03_FluentApiWithBuddy với cấu hình giống hệt của P02_FluentApi.

    Tạo thêm class PersonMap trong file PersonMap.cs và viết code như sau:

    using System.Data.Entity.ModelConfiguration;
    namespace P02_FluentApi
    {
        class PersonMap : EntityTypeConfiguration<Person>
        {
            public PersonMap()
            {
                Property(p => p.FirstName).HasMaxLength(30);
                Property(p => p.LastName).HasMaxLength(30);
                Property(p => p.MiddleName)
                    .HasMaxLength(1)
                    .IsFixedLength()
                    .IsUnicode(false);
            }
        }
    }

    Điều chỉnh lại phương thức OnModelCreating của lớp Context như sau:

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Configurations.Add(new PersonMap());
    }

    Qua ví dụ trên bạn có thể nhận xét:

    Lớp PersonMap vẫn chứa các fluent api để cấu hình như thông thường.

    Tuy nhiên, do lớp này kế thừa từ lớp generic EntityTypeConfiguration<T> (với T thay bằng Person trong ví dụ), bạn không cần chỉ định entity class qua phương thức Entity như khi sử dụng lớp DbModelBuilder. Bạn có thể trực tiếp cấu hình các property tương ứng với phương thức Property.

    Nếu cần cấu hình cho entity, bạn trực tiếp gọi các phương thức cấu hình entity của EntityTypeConfiguration như HasIndex(), HasKey(), v.v.

    Trong phương thức OnModelCreating() giờ chỉ cần khởi tạo object của PersonMap và thêm vào property Configurations của modelBuilder.

    Như vậy, nếu cần cấu hình cho nhiều class với nhiều property, phương pháp này tốt hơn rất nhiều. Bạn có thể tạo ra nhóm buddy class riêng cho dễ quản lý.

    Bonus: các lớp hỗ trợ cấu hình cho kiểu cơ sở

    Khi sử dụng fluent api để cấu hình cho property, tùy thuộc vào kiểu dữ liệu của property, phương thức Property sẽ trả lại kết quả là object của các class khác nhau.

    Ví dụ lời gọi Property(p => p.FirstName) trả lại một object thuộc kiểu StringPropertyConfiguration. StringPropertyConfiguration kế thừa từ LengthPropertyConfiguration. Bản thân LengthPropertyConfiguration lại kế thừa từ PrimitivePropertyConfiguration.

    PrimitivePropertyConfiguration là lớp cha của tất cả các lớp hỗ trợ cấu hình cho các property kiểu cơ sở. Dưới đây là các class kế thừa từ PrimitivePropertyConfiguration:

    Từ LengthPropertyConfiguration sinh ra hai lớp con:

    Phương thức của các class này hỗ trợ cấu hình cho trường dữ liệu tương ứng với các property thuộc các kiểu cơ bản.

    Khi biết các thông tin này, bạn sẽ dễ dàng tra cứu được các phương thức của nó dùng trong cấu hình cho property như bạn đã dùng trong các ví dụ bên trên.

    Kết luận

    Trong bài học này bạn đã biết được cách sử dụng fluent api để cấu hình cho bảng dữ liệu từ entity class. Đây là cách thức cấu hình đầy đủ nhất cho entity class. Cũng lưu ý rằng, bạn hoàn toàn có thể sử dụng kết hợp cả attribute và fluent api trong một entity class.

    + Nếu bạn thấy site hữu ích, trước khi rời đi hãy giúp đỡ site bằng một hành động nhỏ để site có thể phát triển và phục vụ bạn tốt hơn.
    + Nếu bạn thấy bài viết hữu ích, hãy giúp chia sẻ tới mọi người.
    + Nếu có thắc mắc hoặc cần trao đổi thêm, mời bạn viết trong phần thảo luận cuối trang.
    Cảm ơn bạn!

    [/signinlocker]

    Theo dõi
    Thông báo của
    guest

    1 Thảo luận
    Cũ nhất
    Mới nhất
    Phản hồi nội tuyến
    Xem tất cả bình luận
    hoàng tử

    lambda này ad chưa dạy