Local data – Truy vấn dữ liệu cục bộ trong Entity Framework

    0

    Local data – dữ liệu cục bộ là loại dữ liệu được Entity Framework tải về và lưu trữ trong chương trình tương tự một danh sách object thông thường. Local data có vai trò rất quan trọng khi phát triển các ứng dụng desktop. Nó giúp đơn giản hóa việc tương tác với dữ liệu từ giao diện người dùng.

    Tuy nhiên, làm việc với local data có một số điểm khác biệt quan trọng so với trực tiếp viết truy vấn LINQ to Entities trên DbSet như bạn đã học trong bài trước.

    Trong bài học này chúng ta sẽ tìm hiểu sâu hơn về cách làm việc với local data cũng như ứng dụng của nó trong chương trình.

    Khái niệm Local Data

    Trong bài học trước (và các ví dụ nhỏ trong chương trước) bạn đều sử dụng cách thức viết truy vấn (LINQ to Entities) trực tiếp trên object của DbSet của context. Loại truy vấn này luôn được dịch thành truy vấn SQL và gửi tới cơ sở dữ liệu. Như bạn đã biết, kết quả của các truy vấn LINQ to Entities không được lưu trữ. Nghĩa là mỗi lần cần đến dữ liệu bạn lại phải chạy truy vấn.

    Bạn cũng đã biết được một phương pháp lưu trữ dữ liệu sử dụng phương thức ToList. ToList copy dữ liệu thu được vào một danh sách cục bộ để tái sử dụng. Tuy nhiên, cách làm này có một số nhược điểm.

    ToList trên truy vấn LINQ to Entities chỉ lưu trữ những dữ liệu lấy được từ cơ sở dữ liệu. Trong quá trình hoạt động, những object mới có thể đã được thêm vào danh mục quản lý của context nhưng chưa được lưu trở lại cơ sở dữ liệu thì không xuất hiện trong kết quả truy vấn.

    Khi bạn tìm kiếm object, nếu sử dụng Single hoặc First, chỉ những object đã lưu vào cơ sở dữ liệu mới xuất hiện trong kết quả. Bạn có thể sử dụng Find để tìm cả những object đang được context quản lý nhưng chưa được lưu lại cơ sở dữ liệu. Tuy nhiên Find chỉ chấp nhận khóa làm tham số, tức là không thể viết các logic phức tạp hơn để tìm kiếm dữ liệu.

    Từ những vấn đề trên, Entity Framework đưa ra một mô hình lưu trữ dữ liệu cục bộ để hỗ trợ chương trình.

    Trong mô hình này, dữ liệu tải từ bất kỳ truy vấn LINQ to Entities nào sẽ được lưu lại trong chương trình. Bạn có thể truy vấn trực tiếp các object này mà không cần tương tác với cơ sở dữ liệu.

    Context sẽ theo dõi tất cả các object (thêm mới, cập nhật, xóa). Kết quả các truy vấn dữ liệu cục bộ sẽ bao gồm cả các object cũ và object mới.

    Khi cần lưu dữ liệu, context căn cứ vào trạng thái của các object nó quản lý và chỉ tạo ra các truy vấn tương ứng nếu object có thay đổi.

    Local data thực chất chỉ là một danh sách object thông thường, các truy vấn LINQ phát đến nó không còn là LINQ to Entities nữa mà chính là LINQ to Objects quen thuộc. Do đó truy vấn thực hiện nhanh chóng hơn nhiều (vì không cần dịch sang SQL và thực hiện trên cơ sở dữ liệu).

    Local data được lưu ở dạng ObservableCollection. Loại cấu trúc này phát ra sự kiện mỗi khi có sự thay đổi dữ liệu. Do đó nó đặc biệt phù hợp để xây dựng các ứng dụng desktop với cơ chế Data Binding và mô hình sự kiện.

    Ngoài ra khi sử dụng Local data bạn thậm chí có thể tự mình theo dõi sự biến đổi của object thông qua các thông tin theo dõi (tracking) của context. Nó rất có ích nếu bạn cần thực hiện các thao tác khôi phục dữ liệu (ví dụ lỡ bị thay đổi nhầm, giờ bạn muốn trả lại giá trị cũ).

    Lưu trữ Local data – thuộc tính Local của DbSet

    Lớp DbSet có property Local cho phép truy xuất đến dữ liệu cục bộ.

    Để hiểu cách thức làm việc của Local, hãy cùng làm một ví dụ nhỏ:

    using DataAccess;
    using static System.Console;
    
    namespace P02_LocalData
    {
        class Program
        {
            static void Main(string[] args)
            {
                Title = "Local Data";
    
                using (var context = new UniversityContext())
                {
                    Local(context);
                }
    
                ReadKey();
            }
    
            static void Local(UniversityContext context)
            {
                var count = context.Courses.Local.Count;
                WriteLine($"Courses provided in Hogwarts: {count}");
            }
        }
    }

    Để thực hiện các ví dụ trong bài này bạn hãy tạo project P02_LocalData trong solution sử dụng từ bài học trước. Sau đó tự thực hiện cài đặt Entity Framework, tham chiếu sang Models và DataAccess, và thêm connectionStrings node vào App.config. Nếu không nhớ cách làm, bạn có thể xem lại bài học trước.

    Nếu chạy chương trình bạn sẽ thu được kết quả như thế này:

    local data chưa được tải

    Không có dữ liệu nào trong bộ nhớ (dữ liệu cục bộ)! Như bài trước bạn đã thấy, chúng ta có khá nhiều khóa học.

    Giờ hãy điều chỉnh lại phương thức Local như sau:

    static void Local(UniversityContext context)
    {
        foreach(var c in context.Courses)
        {
            WriteLine($"{c.Name}");
        }
    
        var count = context.Courses.Local.Count;
        WriteLine($"Courses provided in Hogwarts: {count}");
    }

    Giờ chạy lại chương trình bạn sẽ thu được kết quả thế này:

    chương trình truy xuất local data

    Qua ví dụ nhỏ trên hẳn bạn có thể đoán ra một số vấn đề.

    Thứ nhất, Local cho phép bạn truy xuất đến dữ liệu được Entity Framework lưu trữ cục bộ (và theo dõi).

    Thứ hai, dữ liệu trong Local chỉ xuất hiện khi truy vấn được thực thi (trên cơ sở dữ liệu). Do cơ chế deferred execution (chỉ khi nào cần đến dữ liệu thì truy vấn mới thực thi), nếu bạn không duyệt qua dữ liệu (trong vòng foreach) thì Local vẫn trống rỗng.

    Chủ động tải dữ liệu với phương thức Load

    Bạn có thể chủ động yêu cầu tải dữ liệu về chương trình và lưu trữ cục bộ sử dụng phương thức Load của DbSet.

    Hãy cùng viết phương thức sau:

    static void Load(UniversityContext context)
    {
        context.Courses.Load(); // chú ý using System.Data.Entity;
        var count = context.Courses.Local.Count;
        WriteLine($"Courses provided in Hogwarts: {count}");
    }
    local data đã được tải

    Load là một phương thức mở rộng (extension method) cho IQueryable<T> và được định nghĩa trong namespace System.Data.Entity. Do đó lưu ý bổ sung using System.Data.Entity; ở đầu file code.

    Ở trên bạn dùng Load để tải trọn vẹn dữ liệu của DbSet. Tuy nhiên, Load có thể tải kết quả của bất kỳ truy vấn LINQ to Entities nào.

    Giả sử bạn chỉ cần tải những khóa học trên 4 tín chỉ, bạn có thể tải dữ liệu theo truy vấn sau:

    static void LoadQuery(UniversityContext context)
    {
        var query = context.Courses.Where(c => c.Credit >= 4);
        query.Load();
        var count = context.Courses.Local.Count;
        WriteLine($"Courses provided in Hogwarts: {count}");
        foreach (var c in context.Courses.Local)
        {
            WriteLine($"{c.Name} ({c.Credit} credits)");
        }
    }

    Cách thức này phù hợp nếu bạn cần lấy dữ liệu nhưng chưa có nhu cầu xử lý ngay lập tức. Ví dụ, nếu bạn phát triển ứng dụng trên windows forms, bạn có thể tải dữ liệu ngay khi form được xuất ra màn hình. Tuy nhiên, người dùng sẽ thực hiện các thao tác trên dữ liệu sau.

    Khi sử dụng Load cần lưu ý: Load tải dữ liệu vào bộ nhớ cục bộ nhưng lại không xóa bỏ kết quả đã lưu từ truy vấn cũ. Ví dụ, nếu bạn đã tải các khóa học trên 4 tín chỉ, sau đó lại tiếp tiếp các khóa học ít hơn 4 tín chỉ, tất cả chúng đều lưu trong Local.

    Truy vấn Local data với LINQ to Objects

    Dữ liệu lưu trong Local thực chất chỉ là danh sách object trong bộ nhớ, bạn có thể tiếp tục sử dụng LINQ để truy vấn.

    Tuy nhiên, điểm khác biệt là, lần này bạn không sử dụng LINQ to Entities như đối với DbSet mà là LINQ to Objects. Nó cũng có nghĩa là truy vấn của bạn không được chuyển thành SQL và thực thi trên cơ sở dữ liệu.

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

    static void LocalQuery(UniversityContext context)
    {
        context.Courses.Load();
    
        WriteLine("### Sorted by name ###");
        var sortedCourses = context.Courses.Local
            .OrderBy(c => c.Name);
        foreach (var c in sortedCourses)
        {
            WriteLine($"{c.Name} ({c.Credit} credits)");
        }
        WriteLine($"** {context.Courses.Local.Count} courses found **");
    
        WriteLine("\r\n### Magic courses ###");
        var magicCourses = context.Courses.Local
            .Where(c => c.Name.Contains("Magic"));
        foreach (var c in magicCourses)
        {
            WriteLine($"{c.Name} ({c.Credit} credits)");
        }
        WriteLine($"** {magicCourses.Count()} courses found **");
    
        WriteLine("\r\n### Grouped by credit ###");
        var groups = context.Courses.Local
            .GroupBy(c => c.Credit)
            .Select(
                g => new 
                { 
                    GroupName = $"{g.Key}-Credit Courses", 
                    GroupItems = g.OrderBy(c => c.Name) 
                }
            );
        foreach(var g in groups)
        {
            WriteLine($"+{g.GroupName}:");
            foreach(var c in g.GroupItems)
            {
                WriteLine($"  -> {c.Name}");
            }
        }
    }

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

    Trong ví dụ trên chúng ta thực hiện lại các thao tác sắp xếp, lọc và phân nhóm dữ liệu sử dụng LINQ to Objects đối với dữ liệu trong Local.

    Bạn không nhận thấy sự khác biệt về provider vì LINQ được thiết kế làm ngôn ngữ truy vấn dữ liệu chung không phụ thuộc vào provider.

    Ứng dụng Local data trong windows forms

    Dữ liệu cục bộ giúp đơn giản hóa việc phát triển ứng dụng windows forms sử dụng cơ sở dữ liệu. Hãy cùng thực hiện một ví dụ nhỏ sau.

    Bước 1. Tạo project mới (P02_WindowsFormsLocalData) thuộc loại Windows Forms App (.NET Framework) và thiết lập làm Startup Project.

    Bước 2. Cài đặt Entity Framework cho project này.

    Bước 3. Cho project tham chiếu sang Models và DataAccess.

    Bước 4. Bổ sung node sau vào App.config:

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

    Bước 5. Tạo Data Source cho project:

    Bước 6. Kéo thả Course data source (tạo ở trên) vào form và thực hiện các thiết kế theo ý bạn

    Bước 7. Viết code behind cho Form1 như sau:

    using System.Data.Entity;
    using System.Windows.Forms;
    using DataAccess;
    
    namespace P02_WindowsFormsLocalData
    {
        public partial class Form1 : Form
        {
            readonly UniversityContext _context = new UniversityContext();
    
            public Form1()
            {
                InitializeComponent();
    
                courseBindingNavigatorSaveItem.Enabled = true;
                courseBindingNavigatorSaveItem.Click += delegate { _context.SaveChanges(); };
    
                _context.Courses.Load();
                courseBindingSource.DataSource = _context.Courses.Local;
            }
        }
    }

    Chạy chương trình bạn sẽ nhanh chóng thu được một ứng dụng windows forms đơn giản sử dụng cơ sở dữ liệu.

    Chương trình nhỏ này thực hiện được tất cả các chức năng CRUD với bảng dữ liệu Courses.

    Như bạn đã thấy, dữ liệu cục bộ lưu trong Local giờ được sử dụng làm nguồn dữ liệu cho BindingSource. Tất cả các thao tác với dữ liệu trên form tác động trực tiếp đến dữ liệu trong Local. Entity Framework theo dõi tất cả những thay đổi trên. Một khi bạn phát lệnh SaveChanges (click vào nút Save) thì mọi thay đổi này sẽ lưu trở lại cơ sở dữ liệu.

    Bạn sẽ học chi tiết hơn về việc theo dõi và lưu thay đổi của Entity Framework trong một bài học riêng.

    Kết luận

    Bài học này đã giúp bạn nắm được cách làm việc với local data, một tính năng rất hữu ích của Entity Framework giúp bạn nhanh chóng xây dựng được các chương trình ứng dụng với cơ sở dữ liệu.

    Bạn có thể tải mã nguồn từ link dưới đây để tham khảo.

    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ề