Data annotation attribute: chỉ định cấu trúc cơ sở dữ liệu

    0

    Data annotation (chú thích dữ liệu) là tên gọi chung của một nhóm attribute đặc biệt giúp chúng ta chỉ định cấu trúc bảng dữ liệu trong code C#. Entity Framework sẽ sử dụng các thông tin này để tạo ra bảng dữ liệu có cấu trúc đúng như bạn mong muốn. Bộ attribute này có vai trò đặc biệt quan trọng nếu muốn code-first sinh ra các cơ sở dữ liệu phức tạp theo đúng yêu cầu.

    Hẳn bạn cũng đã thấy, các kỹ thuật lập trình code-first cơ bản và việc sử dụng quy ước của code-first không đủ để tạo ra cơ sở dữ liệu theo đúng yêu cầu. Ví dụ, (1) làm sao để ràng buộc độ dài của trường văn bản, (2) làm sao để ép một trường không được để trống, (3) làm sao để chỉ định chính xác cấu hình cột dữ liệu trong bảng khi tạo class C# để về sau Entity Framework tạo bảng theo đúng yêu cầu? v.v..

    Khi ánh xạ domain class sang CSDL, đôi khi bạn không muốn tuân theo các quy ước và quy tắc mặc định của Entity Framework. Ví dụ, bạn muốn đặt tên bảng, tên cột theo “kiểu SQL” thay vì “kiểu C#” mặc định theo quy tắc của Entity Framework.

    Bài học này sẽ tiếp tục cung cấp cho bạn các kỹ thuật cao cấp hơn để định nghĩa cấu trúc cơ sở dữ liệu trực tiếp từ code C# sử dụng data annotation attribute. Các kỹ thuật học trong bài sẽ giúp bạn xây dựng được những bảng dữ liệu phức tạp và không cần tuân theo các quy tắc và quy ước của Entity Framework.

    Quay trở lại Tự học lập trình ADO.NET và Entity Framework

    Data Annotation Attributes

    Ví dụ về sử dụng Data Annotation Attribute

    Chúng ta cùng thực hiện một ví dụ minh họa:

    1. Tạo một solution trống mới (S04_Configuration) với một project Console App (P01_Attribute).
    2. Click phải vào node References của project và chọn Add References. Trong cửa sổ Reference Manager chọn node Assemblies và tìm tới System.ComponentModel.DataAnnotations. Thao tác này bổ sung thư viện DataAnnotations cho project.
    assembly chứa các data annotation attribute

    Trong project này tạo thêm file mã nguồn Person.cs cho lớp Person và viết code như sau:

    using System.ComponentModel.DataAnnotations;
    using System.ComponentModel.DataAnnotations.Schema;
    
    namespace P01_Attribute
    {
        public class Person
        {
            public int PersonId { get; set; }
    
            [MaxLength(30)] // FirstName có tối đa 30 ký tự
            public string FirstName { get; set; }
    
            [MaxLength(30)] // LastName có tối đa 30 ký tự
            public string LastName { get; set; }
    
            [StringLength(1, MinimumLength = 1)] // MiddleName có tối đa 1 ký tự
            [Column(TypeName = "char")] // phải dùng kiểu char SQL
            public string MiddleName { get; set; }
        }
    }

    Thực hiện lại các thao tác cài đặt và tạo cơ sở dữ liệu với code first đã học trong bài trước. Sau khi chạy chương trình lần đầu sẽ thu được bảng People với cấu trúc như sau:

    Các cột của bảng tạo ra theo chỉ định của data annotation
    Bản thiết kế của People khi mở trên Visual Studio

    Nếu để ý bạn sẽ thấy kiểu của các trường này đã tuân thủ các yêu cầu đặt ra: FirstName và LastName có kiểu nvarchar, giới hạn 30 ký tự, MiddleName có kiểu char, giới hạn 1 ký tự.

    Data Annotation Attributes

    Một số cú pháp lạ các bạn gặp ở trên như [MaxLength(30)], [StringLength(1, MinimumLength = 1)] hay [Column(TypeName = "char")] được gọi là các data annotation attribute (attribute chú thích dữ liệu).

    Attribute là loại cấu trúc dùng để lưu các thông tin bổ sung cho thành phần khác (như class, method). Các thông tin này được sử dụng ở giai đoạn runtime.

    Các attribute chú thích cho dữ liệu được đặt trong hai namespace System.ComponentModel.DataAnnotationsSystem.ComponentModel.DataAnnotations.Schema của assembly System.ComponentModel.DataAnnotations.dll. Các attribute này được dùng để chỉ định cấu trúc dữ liệu như bạn đã thấy trong ví dụ.

    Ngoài ra nhóm attribute này cũng có tác dụng xác nhận dữ liệu. Ví dụ, nếu tạo một object Person với MiddleName dài hơn 1 ký tự. Khi lưu dữ liệu (SaveChanges) chương trình sẽ phát ngoại lệ (exception) trước khi tạo ra truy vấn SQL. Bạn thậm chí có thể chỉ định thông báo lỗi với attribute:

    [MaxLength(30, ErrorMessage = "Họ không dài quá 30 ký tự")]
    public string FirstName { get; set; }
    
    [MaxLength(30, ErrorMessage = "Tên không dài quá 30 ký tự")]
    public string LastName { get; set; }

    Danh sách Data annotation attribute

    Các attribute trong namespace System.ComponentModel.DataAnnotations

    Tất cả các attribute dưới đây đều áp dụng cho property.

    AttributeÝ nghĩa
    KeyChỉ định thuộc tính sẽ được ánh xạ sang làm khóa chính của bảng. Sử dụng attribute này nếu tên khóa chính không tuân theo quy ước của Entity Framework.
    TimestampChỉ định kiểu dữ liệu của cột (tương ứng với property này) sẽ là rowversion.
    ConcurrencyCheckCột tương ứng cần đưa vào kiểm tra cạnh tranh (theo kiểu optimistic).
    RequiredChỉ định cột tương ứng phải là NotNull.
    MinLengthChỉ định độ dài tối thiểu của cột kiểu văn bản hoặc mảng byte.
    MaxLengthChỉ định độ dài tối đa của cột kiểu văn bản hoặc mảng byte.
    StringLengthChỉ định độ dài tối đa của cột kiểu văn bản.

    Các attribute trong namespace System.ComponentModel.DataAnnotations.Schema

    Các attribute dưới đây có thể áp dụng cho class hoặc property.

    AttributeDescription
    TableÁp dụng cho entity class, chỉ định tên bảng và sơ đồ (schema).
    ColumnÁp dụng cho property, chỉ định tên cột, thứ tự và kiểu dữ liệu của cột.
    IndexChỉ định cột tương ứng cần có Index (từ EF 6.1)
    ForeignKeyChỉ định thuộc tính này sẽ là khóa ngoài. Bạn sẽ gặp lại attribute này trong bài học về quan hệ một – nhiều.
    NotMappedNếu áp dụng cho property chỉ định rằng property này sẽ không ánh xạ sang cột của bảng; Nếu áp dụng cho class chỉ định rằng class sẽ không được ánh xạ sang bảng. Nói cách khác, EF không được sinh ra cột hoặc bảng từ property hoặc class tương ứng.
    DatabaseGeneratedChỉ định rằng giá trị của cột tương ứng sẽ do cơ sở dữ liệu sinh ra.
    InversePropertySử dụng khi cấu hình nhiều quan hệ tương tự nhau giữa hai class. Ví dụ, hai class có cùng lúc hai quan hệ 1 – nhiều.
    ComplexTypeĐánh dấu class là kiểu phức tạp.

    Sử dụng một số attribute đơn giản

    Hãy cùng cải tiến lớp Person như sau:

    using System.ComponentModel.DataAnnotations;
    using System.ComponentModel.DataAnnotations.Schema;
    
    namespace P01_Attribute
    {
        [Table("Person", Schema = "main")]
        public class Person
        {
            [Key]
            [Column("person_code", Order = 1)]
            public int PersonCode { get; set; }
    
            [MaxLength(30, ErrorMessage = "Họ không dài quá 30 ký tự")]
            [Column("first_name", Order = 2)]
            public string FirstName { get; set; }
    
            [MaxLength(30, ErrorMessage = "Tên không dài quá 30 ký tự")]
            [Column("last_name", Order = 4)]
            public string LastName { get; set; }
    
            [StringLength(1, MinimumLength = 1)]
            [Column("middle_name", TypeName = "char", Order = 3)]
            public string MiddleName { get; set; }
    
            [NotMapped]
            public string FullName => $"{FirstName} {MiddleName} {LastName}";
        }
    }

    Bạn thu được CSDL như sau:

    Trong phần ví dụ trên bạn đã sử dụng attribute để thực hiện các cấu hình sau:

    Chỉ định tên bảng

    Bạn muốn đặt tên bảng dữ liệu tương ứng là Person, thay cho People theo quy tắc của Entity Framework. Bạn cũng muốn đặt bảng này trong schema main thay cho dbo mặc định.

     [Table("Person", Schema = "main")]
     public class Person
     { ...

    Như vậy, nếu bạn muốn bảng của CSDL được đặt tên khác với cách đặt tên mặc định của Entity Framework, bạn có thể chỉ định tên thông qua attribute:

    [Table(string name, Properties:[Schema = string]): chỉ định tên bảng và schema.

    Intellisense của Visual Studio có thể giúp bạn đọc hiểu ý nghĩa và cách sử dụng của mỗi attribute.

    Tuy nhiên, để ý cách thức mô tả của attribute có thể bao gồm hai phần: tham số (bắt buộc) và Properties (tùy chọn).
    Như trong attribute Table, tham số là name thuộc kiểu string, còn lại Schema (kiểu string) lại là property.

    Bạn phải cung cấp giá trị cho name nhưng không cần thiết phải phải dùng Schema.

    Ngoài ra, cách truyền giá trị cho tham số và property có chút khác biệt: [Table (“person”, Schema = “main”)]

    Chỉ định tên cột

    Bạn cũng muốn đặt tên trường theo cách thường gặp của Sql Server (viết thường, dùng dấu _ giữa các từ) thay cho cách đặt tên mặc định của Entity Framework. Cụ thể, property FirstName => trường first_name, LastName => last_name, MiddleName => middle_name. Bạn cũng muốn thứ tự các trường trong bảng lần lượt là first_name, middle_name, last_name (trong khi các property lại đặt theo thứ tự FirstName, LastName, MiddleName).

    Khi đó bạn cần sử dụng attribute [Column] theo cấu trúc sau: [Column (string name, Properties:[Order = int],[TypeName = string])

    [Column("first_name", Order = 1)]
    public string FirstName { get; set; }
    
    [Column("last_name", Order = 3)]
    public string LastName { get; set; }
    
    [Column("middle_name", TypeName = "char", Order = 3)]
    public string MiddleName { get; set; }

    Chỉ định khóa chính

    Nếu bạn không muốn sử dụng tên khóa chính theo quy ước của code-first (Id hoặc PersonId), bạn có thể dùng attribute [Key] trước một trường kiểu int để báo cho Entity Framework biết đây sẽ là khóa chính của bảng tương ứng.

    [Key]
    [Column("person_code")]
    public int PersonCode { get; set; }

    Ở đây, PersonCode được ánh xạ sang cột person_code và trở thành trường khóa chính của bảng CSDL tương ứng.

    Không ánh xạ property thành cột

    Trong class bạn có thể tạo ra một số property mà giá trị của nó được tính toán tự động từ các property khác. Loại property này thường được gọi là calculated property. Ví dụ ở trên, FullName được sinh ra từ FirstName, MiddleName và LastName. FullName là một ví dụ của calculate property.

    Bạn chắc sẽ không muốn ánh xạ FullName thành một cột của CSDL làm gì. Trong trường hợp đó bạn sử dụng attribute [NotMapped]:

    [NotMapped]
    public string FullName => $"{FirstName} {MiddleName} {LastName}";

    Kết luận

    Bài học này đã cung cấp cho bạn cách thức thứ nhất để chỉ định cấu trúc bảng dữ liệu từ C# class với code-first.

    Lưu ý rằng, bài học chỉ mang tính chất giới thiệu chung về cách thức làm việc với attribute cũng như giới thiệu một số attribute đơn giản. Bạn sẽ lần lượt làm việc với các attribute trong danh sách trên khi học từng chủ đề riêng. Vì vậy, bạn không cần cố gắng học thuộc hay vội tìm hiểu chi tiết từng attribute.

    Bạn có thể để ý rằng, một số vấn đề mà data annotation chưa giải quyết được như xác định quan hệ giữa các bảng. Ngoài ra, việc sử dụng quá nhiều data annotation dẫn đến class nhìn rất rối, nhất là khi có nhiều attribute trên cùng một property.

    Trong bài học tiếp theo chúng ta sẽ xem xét một cách khác để thay cho data annotation khi cần thiết.

    Nếu có thắc mắc hoặc cần trao đổi thêm, mời bạn viết trong phần Bình luận ở cuối trang. Nếu cần trao đổi riêng, hãy gửi email hoặc nhắn tin qua form liên hệ. Nếu bài viết hữu ích với bạn, hãy giúp chúng tôi chia sẻ tới mọi người. Cảm ơn bạn!

    Bình luận

    avatar
      Đăng ký theo dõi  
    Thông báo về