Lazy loading trong Entity Framework – tự động tải dữ liệu quan hệ

    0

    Lazy loading là một trong số các cơ chế tải dữ liệu quan hệ trong Entity Framework. Lazy loading hỗ trợ tự động tải dữ liệu cho các navigation property khi bạn truy xuất đến nó. Đây là cơ chế đơn giản nhất giúp bạn làm việc với các entity trong quan hệ “has” như một – một, một – nhiều, nhiều – nhiều.

    Giới thiệu chung về cách tải dữ liệu quan hệ

    Trong các bài học trước chúng ta mới chỉ học cái truy vấn dữ liệu thuộc về một kiểu (tương ứng là dữ liệu từ một bảng). Tuy nhiên dữ liệu trong thực tế tồn tại nhiều loại quan hệ.

    Như đã học ở chương về xây dựng mô hình EDM bạn đã biết rằng giữa các entity có thể tồn tại hai loại quan hệ: hasis.

    Entity Framework hỗ trợ bạn tạo các loại quan hệ trên giữa các entity, và qua đó ánh xạ thành quan hệ giữa các bảng.

    Trong loạt bài học này chúng ta sẽ xem xét cách truy vấn dữ liệu quan hệ. Cụ thể chúng ta sẽ xem xét cách tải dữ liệu từ các bảng có quan hệ “has” và quan hệ “is”.

    Đối với quan hệ “has”, chúng ta sẽ tìm hiểu các cơ chế tải dữ liệu quan hệ với LINQ to Entities, bao gồm lazy loading, eager loading và explicit loading.

    Đối với quan hệ “is”, chúng ta sẽ tìm hiểu truy vấn đa hình (polymorphic query).

    Trước hết trong bài học này chúng ta sẽ xem xét chi tiết cách tải dữ liệu quan hệ với Lazy loading.

    Để thực hiện các ví dụ trong bài học này, bạn hãy tạo một project mới (P02_RelatedData) trong solution đã sử dụng từ bài trước. Sau đó hãy thực hiện các bước cài đặt tương tự để sử dụng Entity Framework cùng với thư viện Models và DataAccess.

    Lazy loading trong Entity Framework là gì?

    Lazy loading là cơ chế tải dữ liệu quan hệ tự động khi bạn truy xuất đến các object có quan hệ.

    Lấy ví dụ, Course có quan hệ với Teacher (1 teacher – n courses) và Student (n courses – n students). Quan hệ này được thể hiện bởi 2 navigation property của Course: Teacher và Students.

    Xem lại bài học thiết kế EDM nếu bạn không nhớ cấu trúc của mô hình này.

    Nếu bạn đã tải một object của Course, các dữ liệu có quan hệ là Students và Teacher không được tải lên cùng. Chỉ khi bạn truy xuất đến navigation property Teacher, dữ liệu của giáo viên giảng dạy khóa học đó mới được Entity Framework tự động tải thêm.

    Quá trình này diễn ra tự động và hoàn toàn ẩn đi những gì diễn ra bên dưới.

    Hãy cùng thực hiện một ví dụ:

    using System;
    using System.Linq;
    using DataAccess;
    using static System.Console;
    
    namespace P03_RelatedData
    {
        class Program
        {
            static void Main(string[] args)
            {
                Title = "HOGWARTS SCHOOL";
    
                using (var context = new UniversityContext())
                {
                    LazyLoading(context);
                }
    
                ReadKey();
            }
    
            static void LazyLoading(UniversityContext context)
            {
                var course = context.Courses.FirstOrDefault(c => c.Name.Contains("Magic"));
                ForegroundColor = ConsoleColor.Green;
                WriteLine($"Course name: {course.Name.ToUpper()}");
                ResetColor();
    
                if (course.Teacher != null)
                {
                    var t = course.Teacher;
                    ForegroundColor = ConsoleColor.Yellow;
                    WriteLine($"by {t.Qualification} {t.FirstName} {t.LastName}");
                    ResetColor();
                }
    
                if (course.Students != null && course.Students.Count > 0)
                {
                    WriteLine("# Students enrolled in this course:");
                    foreach (var s in course.Students)
                    {
                        WriteLine($"  -> {s.FirstName} {s.LastName}");
                    }
                }
    
            }
        }
    }
    truy xuất dữ liệu quan hệ với lazy loading

    Cơ chế lazy loading sẽ được sử dụng nếu navigation property tương ứng được khai báo là virtual. Nếu nhìn lại code của lớp Course (thư viện Models) bạn sẽ thấy Teacher và Students đều được khai báo là virtual property:

    using System.Collections.Generic;
    
    namespace Models
    {
        public partial class Course
        {
            public Course()
            {
                this.Students = new HashSet<Student>();
            }
        
            public int Id { get; set; }
            public string Name { get; set; }
            public int Credit { get; set; }
            public string Description { get; set; }
        
            public virtual Teacher Teacher { get; set; }
            public virtual ICollection<Student> Students { get; set; }
        }
    }

    Do vậy, cơ chế lazy loading đang được sử dụng khi bạn tải object của Course và sau đó truy xuất đến property Teacher hoặc Students.

    Nếu bạn xóa bỏ từ khóa virtual trước định nghĩa của Teacher và Students, sau đó dịch và chạy lại chương trình sẽ thu được kết quả như sau:

    tăt chế độ lazy loading

    Trong trường hợp này, cơ chế lazy loading không hoạt động. Các dữ liệu có quan hệ với Course không được tải vào chương trình.

    Cơ chế hoạt động của lazy loading

    Để thực hiện cơ chế lazy loading, Entity Framework sử dụng một mô hình có tên gọi là dynamic proxy. Mô hình này hoạt động như sau:

    Khi nhận được kết quả từ truy vấn, Entity Framework tạo ra các object (instance) của entity class với dữ liệu thu được. Đồng thời, Entity Framework tự động tạo ra một số class mới (lúc chạy chương trình) kế thừa từ entity class do bạn thiết kế.

    Các class tạo động trong lúc chạy đó được gọi là các proxy. Khi truy xuất đến entity object, thực tế là bạn đang truy xuất thông qua proxy.

    Trong proxy class, Entity Framework ghi đè (override) các virtual navigation property để bổ sung các logic tải thêm dữ liệu khi bạn truy xuất các property này.

    Bạn có thể đọc thêm bài viết này để nắm rõ hơn về mẫu thiết kế proxydynamic proxy tool.

    Nếu navigation property không được khai báo với từ khóa virtual, dynamic proxy không thể ghi đè nó được, và do đó cũng không thể bổ sung thêm các lệnh tải dữ liệu.

    Cũng vì lý do này, lớp entity cũng không được khai báo với từ khóa sealed (cấm kế thừa) nếu bạn muốn dùng lazy loading.

    Duyệt danh sách với lazy loading

    Trong ví dụ trên bạn chỉ tải một object của Course và truy xuất các property có quan hệ sử dụng lazy loading.

    Giả sử bạn cần tải tất cả (hoặc một danh sách) khóa học và in ra các thông tin liên quan tương tự. Tình huống sẽ hơi khác đi một chút.

    Hãy cùng code ví dụ sau:

    static void LazyLoadingMARS(UniversityContext context)
    {
        foreach(var course in context.Courses)
        {
            ForegroundColor = ConsoleColor.Green;
            Write($"{course.Name.ToUpper()}");                
    
            if (course.Teacher != null)
            {
                var t = course.Teacher;
                ForegroundColor = ConsoleColor.Yellow;
                WriteLine($" ({t.Qualification} {t.FirstName} {t.LastName})");
                ResetColor();
            }
        }
    }

    Đây là một method rất bình thường có nhiệm vụ tải hết tất cả các khóa học sau đó in ra tên khóa học và giáo viên giảng dạy.

    Tuy nhiên, nếu chạy chương trình bạn sẽ gặp lỗi “InvalidOperationExceptionThere is already an open DataReader associated with this Command which must be
    closed first.
    “.

    Hãy yên tâm, bạn không phải là người duy nhất gặp tình huống này.

    Đây không phải là lỗi của bạn hay của Entity Framework. Đây là một đặc tính của SQL Server có tên gọi là Multiple Active Result Sets (MARS).

    Bình thường SQL Server chỉ cho phép tại mỗi thời điểm thực hiện một truy vấn trên một kết nối, tắc là mặc định MARS có giá trị false. Tuy nhiên, khi bạn duyệt danh sách như trên, nhiều truy vấn sẽ phát đi cùng lúc, do đó sẽ gặp lỗi.

    Do vậy bạn cần chỉ định rõ là muốn sử dụng chế độ MARS trên SQL Server. Cách thực hiện rất đơn giản: bổ sung thêm tham số MultipleActiveResultSets=true; vào connection string như sau:

    <connectionStrings>
        <add name="UniversityContext" providerName="System.Data.SqlClient" connectionString="Data Source=(localdb)\mssqllocaldb;Initial Catalog=UniversityDatabase;Integrated Security=True; MultipleActiveResultSets=true;"/>
      </connectionStrings>

    Khi này bạn có thể chạy chương trình:

    lazy loading khi duyệt danh sách

    Nhược điểm của lazy loading

    Mặc dù lazy loading giúp bạn dễ dàng tải các dữ liệu quan hệ, nó cũng có thể gây ra nhiều hậu quả nếu sử dụng không hợp lý.

    Vấn đề nằm ở chỗ, mỗi khi bạn truy xuất đến một navigation property, Entity Framework phải thực hiện truy vấn mới đến cơ sở dữ liệu.

    Hãy tưởng tượng một entity có nhiều quan hệ với các entity khác, đặc biệt là quan hệ 1 – nhiều hoặc nhiều – nhiều. Khi bạn truy xuất đến mỗi navigation property (nhưng không thực sự cần đến nó), một truy vấn sẽ phát đi đến cơ sở dữ liệu. Tổng hợp lại bạn có thể (vô tình) gây ra rất nhiều truy vấn đến cơ sở dữ liệu.

    Do lazy loading thực hiện mọi thứ hoàn toàn tự động, bạn thậm chí không hề hay biết những gì đang diễn ra.

    Trong tình huống cần kiểm soát chặt chẽ những dữ liệu quan hệ sẽ được tải, bạn nên sử dụng chế độ eager loading hoặc explicit loading (sẽ xem xét trong các bài tiếp theo).

    Nếu không muốn sử dụng chế độ lazy loading, bạn thậm chí có thể tắt nó đi bằng cách sau:

    context.Configuration.LazyLoadingEnabled = false;

    Khi này, dù ở entity class bạn dùng virtual thì chế độ lazy loading cũng bị ngắt sau đoạn code trên (cho đến khi nào bạn bật trở lại).

    Kết luận

    Trong bài học này chúng ta đã tìm hiểu cách truy vấn dữ liệu quan hệ sử dụng cơ chế lazy loading.

    Dễ dàng thấy được, khi sử dụng lazy loading, việc tải dữ liệu quan hệ trong Entity Framework khá đơn giản. Tuy nhiên bạn cần lưu ý xem thiết kế EDM có hỗ trợ lazy loading hay không. Trong trường hợp cần thiết để đảm bảo hiệu suất, bạn có thể tắt chế độ lazy loading.

    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ề