Explicit loading trong Entity Framework – tải dữ liệu quan hệ bán tự động

    0

    Explicit loading là một cơ chế tải dữ liệu quan hệ đặc biệt trong Entity Framework. Nó hoạt động gần giống lazy loading ở chỗ, dữ liệu quan hệ được tải riêng biệt sau khi tải dữ liệu chính. Tuy nhiên nó lại khác biệt ở chỗ quá trình này không diễn ra tự động mà bạn phải tự mình gọi phương thức tải dữ liệu.

    Trong bài học này bạn sẽ tìm hiểu kỹ về phương pháp tải dữ liệu này. Có thể nói ngay, đây là phương pháp tải dữ liệu có nhiều ưu điểm và dung hòa được hai phương pháp bạn đã biết (lazy loading và eager loading).

    Để thực hành, bạn tiếp tục sử dụng project P03_RelatedData đã sử dụng từ hai bài học về lazy loadingeager loading.

    Explicit loading

    Để hình dung được cách làm việc với Explicit loading, hãy cùng code một ví dụ:

    static void ExplicitLoading(UniversityContext context)
    {
        var course = context.Courses.FirstOrDefault(c => c.Name.Contains("Magic"));
        var entry = context.Entry(course);
        entry.Collection("Students").Load();
        entry.Reference(c => c.Teacher).Load();
    
        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}");
            }
        }
    }

    Kết quả chạy chương trình

    tải dữ liệu quan hệ với explicit loading

    Phương thức trên rất quen thuộc vì bạn đã thực hiện trong bài học về lazy loading. Tuy nhiên để ý các dòng lệnh:

    var entry = context.Entry(course);
    entry.Collection("Students").Load();
    entry.Reference(c => c.Teacher).Load();

    Các lệnh này sử dụng phương thức Entry của DbContext với tham số là object của Course thu được từ truy vấn LINQ to Entities. Từ đó bạn có thể tiếp tục gọi phương thức Collection để tải dữ liệu quan hệ ở dạng danh sách object, hoặc Reference nếu dữ liệu quan hệ chỉ là một object.

    Trong ví dụ trên, Students là một danh sách, còn Teacher chỉ là một object. Do đó bạn dùng Collection để tải danh sách Student theo học Course đó, và dùng Reference để tải object của Teacher giảng dạy Course.

    Khi này Entity Framework chỉ phát đi 3 truy vấn: Tải object Course trong truy vấn chính; Tải danh sách sinh viên và tải object Teacher theo yêu cầu của hai phương thức Load.

    Một số vấn đề khi sử dụng explicit loading

    Giống như trường hợp Include của eager loading, phương thức Collection và Reference cũng có phiên bản string:

    entry.Collection("Students").Load(); // tương đương entry.Collection(c=>c.Students)
    entry.Reference("Teacher").Load(); // tương đương entry.Reference(c=>c.Teacher)

    Nếu như tên navigation property không đến từ tham số, bạn nên sử dụng phiên bản lambda để tận dụng khả năng kiểm tra lỗi. Ngoài ra, khi sử dụng phiên bản string có thể sẽ phát sinh vấn đề nếu như tên property trong entity class thay đổi.

    Explicit loading chỉ có thể tải các object có quan hệ trực tiếp với object chính. Điều này khác biệt với eager loading có thể tải nhiều cấp độ quan hệ.

    Như trường hợp của Course, bạn chỉ có thể tải Students và Teacher (là hai navigation property của Course). Bạn không thể tải thêm Department như khi sử dụng eager loading.

    Mặc dù về cú pháp bạn vẫn có thể viết các lệnh Collection và Reference với nhiều cấp quan hệ, chương trình dịch bình thường nhưng khi chạy chương trình sẽ báo lỗi:

    // lệnh này sẽ phát ra lỗi khi chương trình chạy (nhưng không có lỗi khi compile)
    context.Entry(course).Reference(c => c.Teacher.Department).Load();

    Phương thức Reference và Collection cho phép kiểm tra xem các object quan hệ đã được tải lên chưa sử dụng property IsLoaded:

    static void ExplicitLoadingTestIsLoaded(UniversityContext context)
    {
        var course = context.Courses.FirstOrDefault(c => c.Name.Contains("Magic"));
        var entry = context.Entry(course);
    
        WriteLine($"Before: {entry.Collection(c => c.Students).IsLoaded}");
    
        entry.Collection(c => c.Students).Load();
    
        WriteLine($"After: {entry.Collection(c => c.Students).IsLoaded}");
    }

    Ví dụ trên thể hiện rõ rằng, nếu không phát lệnh Load, dữ liệu quan hệ sẽ không được tải.

    Bạn có thể sử dụng IsLoaded để kiểm tra trước khi quyết định phát lệnh Load để tránh thực hiện lệnh tải dư thừa.

    Truy vấn collection navigation property với Explicit loading

    Một lợi thế rất lớn khi sử dụng explicit loading là bạn có thể giới hạn object của collection navigation property được tải về.

    Lấy ví dụ, khi có được object Course, bạn không muốn tải toàn bộ danh sách sinh viên tham gia vào course đó mà chỉ muốn lấy một nhóm sinh viên trong đó.

    Dĩ nhiên, bạn hoàn toàn có thể tải toàn bộ sinh viên tham gia course vào chương trình rồi dùng LINQ to Objects để lọc dữ liệu. Nhưng tại sao phải lấy dữ liệu dư thừa, đặc biệt nếu danh sách object trong quan hệ rất lớn?

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

    static void ExplicitLoadingCollectionQuery(UniversityContext context)
    {
        context.Configuration.LazyLoadingEnabled = false;
        var course = context.Courses.FirstOrDefault(c => c.Name.Contains("Magic"));
        var entry = context.Entry(course);
        var query = entry.Collection(c => c.Students).Query();
        var gryffindor = query.Where(s => s.Group == "Gryffindor");
        gryffindor.Load();
    
        ForegroundColor = ConsoleColor.Green;
        WriteLine($"Course name: {course.Name.ToUpper()}");
    
        ForegroundColor = ConsoleColor.Cyan;
        WriteLine("# Gryffindor students enrolled in this course:");
        ResetColor();
        foreach (var s in gryffindor)
        {
            WriteLine($"  -> {s.FirstName} {s.LastName}");
        }
    }

    Hãy để ý nhóm lệnh sau:

    var entry = context.Entry(course);
    var query = entry.Collection(c => c.Students).Query();
    var gryffindor = query.Where(s => s.Group == "Gryffindor");
    gryffindor.Load();

    Phương thức Query giúp bạn thu được một truy vấn nhưng chỉ tác động trên collection navigation property của object chính. Từ đây bạn có thể viết thêm bất kỳ truy vấn LINQ to Entities nào mình biết để lọc lấy những object cần thiết. Phương thức Load giúp bạn chủ động yêu cầu tải dữ liệu.

    Lưu ý: Nếu bạn không gọi Load, chương trình vẫn chạy bình thường. Tuy nhiên khi đó cơ chế lazy loading lại được sử dụng (khi bạn bắt đầu duyệt qua danh sách sinh viên).

    Kết quả là chỉ những object (trong quan hệ) thỏa mãn điều kiện mới được tải về. Cách kết hợp như trên có tên gọi là filtered explicit load.

    Trong ví dụ trên chúng ta chỉ tải những sinh viên từ nhà Gryffindor đã tham gia khóa học.

    Một tình huống khác bạn có thể gặp là khi bạn chỉ muốn lấy một số thông tin thống kê từ danh sách object có quan hệ. Ví dụ, bạn chỉ muốn biết có bao nhiêu sinh viên tham gia vào khóa học chứ không cần toàn bộ danh sách sinh viên.

    Trong những tình huống tương tự bạn cũng có thể lợi dụng kỹ thuật như sau:

    static void ExplicitLoadingCollectionAggregation(UniversityContext context)
    {
        context.Configuration.LazyLoadingEnabled = false;
        var course = context.Courses.FirstOrDefault(c => c.Name.Contains("Magic"));
        var entry = context.Entry(course);
        var query = entry.Collection(c => c.Students).Query();
        var count = query.Count();            
    
        ForegroundColor = ConsoleColor.Green;
        Write($"Course name: {course.Name.ToUpper()}");
        ForegroundColor = ConsoleColor.Yellow;
        WriteLine($" ({count} students enrolled)");
    }

    Chúng ta vẫn sử dụng cách tạo ra truy vấn từ phương thức Query nhưng sau đó sử dụng phương thức Count để lấy tổng số. Khi này truy vấn gửi về cơ sở dữ liệu sẽ không lấy toàn bộ dữ liệu nữa mà chỉ lấy tổng số bản ghi đang có (SQL COUNT).

    var entry = context.Entry(course);
    var query = entry.Collection(c => c.Students).Query();
    var count = query.Count();

    Kết luận

    Trong bài học này bạn đã học kỹ thuật tải dữ liệu quan hệ Explicit loading. Phương thức này có một số ưu điểm so với lazy loading và eager loading bạn nên để ý:

    Bạn không cần đánh dấu virtual cho navigation property (yêu cầu bắt buộc của lazy loading). Điều này rất có ích nếu bạn phải sử dụng các model class có sẵn mà bạn không thể can thiệp vào code để điều chỉnh.

    Bạn kiểm soát được khi nào cần tải dữ liệu quan hệ. Việc tải dữ liệu quan hệ diễn ra độc lập so với tải dữ liệu chính (ưu điểm của lazy loading) đồng thời hạn chế việc sử dụng JOIN (tránh nhược điểm của eager 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 Thảo luận cuối trang.
    Nếu cần trao đổi riêng với chúng tôi, 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.
    Đăng ký theo dõi trang facebook để nhận thông tin về bài viết mới.
    Tắt Adblocker hoặc whitelist trang để hỗ trợ chúng tôi.
    Cảm ơn bạn!

    * Bản quyền bài viết thuộc về Tự học ICT. Đề nghị tôn trọng bản quyền. DMCA.com Protection Status
    Subscribe
    Notify of
    guest
    0 Thảo luận
    Inline Feedbacks
    View all comments