Quan hệ một – một trong Entity Framework

    0

    Bài học này sẽ hướng dẫn bạn cách xây dựng quan hệ một – một giữa các class để Entity Framework có thể ánh xạ chính xác thành hai bảng có quan hệ phù hợp. Để tạo quan hệ một – một, bạn có thể sử dụng quy ước (convention) của code-first kết hợp Attribute hoặc sử dụng fluent API.

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

    Quan hệ một – một không thực sự phổ biến trong CSDL như hai loại quan hệ một – nhiềunhiều – nhiều mà bạn đã học. Tuy nhiên bạn vẫn cần sử dụng đến nó nếu model class có nhiều loại dữ liệu cần tách riêng ra để dễ quản lý, bảo trì và để đảm bảo tuân thủ các nguyên tắc thiết kế hướng đối tượng (như nguyên lý SRP – Single Responsibility – của bộ nguyên lý SOLID).

    Lấy ví dụ, một người thường có một nhóm dữ liệu liên lạc bao gồm địa chỉ, email, số điện thoại, mạng xã hội, website cá nhân, online portfolio url, v.v.. Những thông tin này có thể gộp chung vào trong cùng class Person. Tuy nhiên, bạn nên tách nó ra thành một class riêng Contact để dễ theo dõi, quản lý và bảo trì về sau. Khi này bạn cần xây dựng quan hệ giữa PersonContact, trong đó mỗi Person có thể có một Contact (với các thông tin như đã phân tích).

    Bạn cũng có thể muốn tạo ra cho mỗi Person một tài khoản riêng Account trên hệ thống với tên user và mật khẩu. Khi đó mỗi Person cũng có thể có một Account.

    Bạn có lẽ không muốn gộp hết các property của AccountContact vào cùng lớp Person chứ?

    Xây dựng quan hệ một – một sử dụng convention và attribute

    Hãy cùng thực hiện một ví dụ. Tạo project mới P04_OneToOne và cài đặt Entity Framework cho project này. Viết code cho file Program.cs như sau:

    using System.ComponentModel.DataAnnotations.Schema;
    using System.Data.Entity;
    
    namespace P04_OneToOne
    {
        public class Person
        {
            public int Id { get; set; }
            public string FirstName { get; set; }
            public string LastName { get; set; }
    
            public virtual Contact Contact { get; set; }
        }
    
        public class Contact
        {
            [ForeignKey("Person")]
            public int Id { get; set; }
            public string Address { get; set; }
            public string Email { get; set; }
            public string Phone { get; set; }
            public string Url { get; set; }
    
            public virtual Person Person { get; set; }
        }
    
        public class Context : DbContext
        {
            public Context() : base("OneToOne")
            {
                var initializer = new DropCreateDatabaseAlways<Context>();
                Database.SetInitializer(initializer);
            }
    
            public DbSet<Person> People { get; set; }
            public DbSet<Contact> Contacts { get; set; }
        }
    
        class Program
        {
            static void Main(string[] args)
            {
                using (var context = new Context())
                {
                    context.Database.Initialize(force: false);
                }
            }
        }
    }

    Chạy chương trình và bạn sẽ thu được CSDL có cấu trúc như sau:

    Trong ví dụ trên chúng ta đã tạo quan hệ một – một giữa bảng Contacts và People (giữa class Contact và Person) trong đó People là bảng chính.

    Trước hết hãy để ý đến cấu trúc bảng Contacts. Trường Id của nó khá đặc biệt: nó vừa đóng vai trò khóa chính (Primary Key, PK) của Contacts nhưng đồng thời lại đóng vai trò khóa ngoài (Foreign Key, FK) của chính bảng này. Với vai trò FK, nó tham chiếu sang PK của bảng People. Đây chính là yêu cầu tạo ra quan hệ một – một của CSDL: một trường phải đồng thời đóng vai trò khóa chính và khóa ngoài (tới bảng chính).

    Như vậy, khi cấu hình class Contact (class “phụ thuộc”), chúng ta phải thực hiện sao cho khóa chính của Contact phải đồng thời làm khóa ngoài của chính nó và tham chiếu sang khóa chính của Person. Do vậy chúng ta sử dụng Attribute [ForeignKey("Person")] trước Id của Contact.

    Lưu ý, để sử dụng attribute ForeignKey, bạn cần tham chiếu tới assembly System.ComponentModel.DataAnnotations.

    Property Id của Contact do đã tuân theo quy ước của code-first nên Entity Framework tự động đặt nó làm PK của Contacts.

    Ngoài ra, quan hệ giữa Person và Contact cũng được xác định thông qua hai navigation property tương ứng:

    // trong class Person
    public virtual Contact Contact { get; set; }
    
    // trong class Contact
    public virtual Person Person { get; set; }

    Lưu ý rằng các navigation property trên mỗi class là bắt buộc. Nếu thiếu một trong hai, Entity Framework sẽ báo lỗi lúc tạo CSDL. Chúng ta cũng khai báo các property với từ khóa virtual để sử dụng cơ chế lazy loading.

    Với cấu hình như trên, Person có thể có một Contact, nhưng Contact bắt buộc phải liên kết với một Person.

    Xây dựng quan hệ một – một sử dụng fluent API

    Nếu không muốn dùng attribute ForeignKey, bạn cũng có thể cấu hình quan hệ 1 – 1 trong lớp Context sử dụng fluent API.

    Hãy ghi đè phương thức OnModelCreating như sau:

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Person>()
            .HasOptional(p => p.Contact)
            .WithRequired(c => c.Person);
    }

    Để cấu hình quan hệ một – một, bạn xuất phát từ class “chính” (Person): modelBuilder.Entity<Person>(). Sau đó chỉ định quan hệ (không bắt buộc) từ Person sang Contact: HasOptional(p => p.Contact), và chỉ định quan hệ (bắt buộc) từ Contact về Person: WithRequired(c => c.Person).

    Như đã quen thuộc từ các bài trước, việc chỉ định quan hệ này thực chất là chỉ định navigation property.

    Cùng với logic trên bạn có thể tiếp tục xây dựng class Account và tạo quan hệ 1 – 1 với Person. Bạn có thể tự tạo class Account hoặc tham khảo trong mã nguồn (link download ở cuối bài).

    Tạo và truy xuất object trong quan hệ một – một

    Với quan hệ tạo ra như trên, bạn có thể viết code để thêm và truy xuất dữ liệu như sau:

    internal class Program
    {
        private static void Main(string[] args)
        {
            using (var context = new Context())
            {
                context.Database.Initialize(force: false);
    
                var trump = new Person
                {
                    FirstName = "Donald",
                    LastName = "Trump",
                    Contact = new Contact
                    {
                        Address = "Washington DC",
                        Email = "donald.trump@gmail.com",
                        Phone = "0123.456.789"
                    },
                    Account = new Account
                    {
                        UserName = "trump",
                        Password = "******"
                    }
                };
                context.People.Add(trump);
    
                var obama = new Person { FirstName = "Barack", LastName = "Obama" };
                var contact = new Contact { Address = "Washington DC", Email = "barack.obama@gmail.com" };
                var account = new Account { UserName = "obama", Password = "***" };
                obama.Account = account;
                obama.Contact = contact;
                context.People.Add(obama);
    
                var bush = new Person { FirstName = "George", LastName = "Bush" };
                var bushContact = new Contact { Address = "Washington DC", Email = "george.bush@gmail.com", Person = bush };
                var bushAccount = new Account { UserName = "bush", Person = bush };
                bush.Account = bushAccount;
                bush.Contact = bushContact;
                context.People.Add(bush);
    
                context.SaveChanges();
    
                foreach (var p in context.People)
                {
                    Console.WriteLine($"{p.FirstName} {p.LastName}, from {p.Contact.Address}");
                    Console.WriteLine($"Account: {p.Account.UserName}");
                }
    
                foreach (var c in context.Contacts)
                {
                    Console.WriteLine($"{c.Person.FirstName} {c.Person.LastName}, from {c.Address}");
                    Console.WriteLine($"Account: {c.Person.Account.UserName}");
                }
            }
    
            Console.ReadKey();
        }
    }

    Bạn để ý thấy, khi tạo object của Person, bạn đồng thời khởi tạo object của các class có quan hệ. Bạn cũng có thể tạo các object của Contact và Account riêng biệt rồi tham chiếu tới từ property tương ứng của Person.

    Do trong cấu hình các class bạn sử dụng lazy loading, Entity Framework tự động tải các object liên quan giúp bạn. Bạn không cần tự mình gọi phương thức Include để tải object liên quan.

    Kết luận

    Bài học này đã hướng dẫn bạn cách tạo quan hệ một – một trong Entity Framework sử dụng convention, attribute và fluent API.

    Lưu ý rằng, nếu sử dụng convention bạn phải kết hợp với attribute ForeignKey để chỉ định khóa ngoà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 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ề