Truy vấn trong Entity Framework Core

Truy vấn trong Entity Framework Core vẫn giống như truy vấn trong Entity Framework 6.x, với các truy vấn SQL được tối ưu hóa hơn và khả năng đưa các phương thức C# / VB.NET vào các truy vấn LINQ-to-Entities.

Truy cập bài viết về LINQ-to- Entity để tìm hiểu thêm về những điều cơ bản của truy vấn trong Entity Framework tại đây.

LINQ-to-Entities trong Entity Framework | Comdy
LINQ-to-Entities trong Entity Framework là gì? Hướng dẫn sử dụng LINQ-to-Entities trong Entity Framework.

Ở bài viết này, bạn sẽ tìm hiểu các tính năng mới của truy vấn được giới thiệu trong Entity Framework Core.

Phương thức C# / VB.NET trong truy vấn

EF Core có một tính năng mới trong LINQ-to-Entities nơi chúng ta có thể thêm các phương thức C# hoặc VB.NET trong truy vấn. Điều này là không thể trong EF 6.


private static void Main(string[] args)
{
    var context = new SchoolContext();
    var studentsWithSameName = context.Students
                                      .Where(s => s.FirstName == GetName())
                                      .ToList();
}

public static string GetName() 
{
    return "Bill";
}

Trong truy vấn LINQ-to-Entities ở trên, chúng tôi đã sử dụng phương thức GetName() của C# trong mệnh đề Where. Điều này sẽ thực hiện các truy vấn sau trong cơ sở dữ liệu:


exec sp_executesql N'SELECT [s].[StudentId], [s].[DoB], [s].[FirstName], 
    [s].[GradeId], [s].[LastName], [s].[MiddleName]
FROM [Students] AS [s]
WHERE [s].[FirstName] = @__GetName_0',N'@__GetName_0 nvarchar(4000)',
    @__GetName_0=N'Bill'
Go

Eager Loading trong EF Core

Eager Loading là gì?

Eager Loading là quá trình trong đó một truy vấn cho một kiểu thực thể cũng tải các thực thể liên quan như một phần của truy vấn, do đó chúng ta không cần phải thực hiện một truy vấn riêng cho các thực thể liên quan.

Entity Framework Core hỗ trợ Eager Loading các thực thể liên quan, giống như EF 6, sử dụng phương thức mở rộng Include() và truy vấn tạo kết quả đầu ra.

Ngoài ra, nó cũng cung cấp phương thức mở rộng ThenInclude() để tải nhiều cấp độ của các thực thể liên quan. (EF 6 không hỗ trợ phương thức ThenInclude())

Phương thức Include trong EF Core

Không giống như EF 6, chúng ta có thể chỉ định biểu thức lambda làm tham số trong phương thức Include() để chỉ định thuộc tính điều hướng như dưới đây.


var context = new SchoolContext();

var studentWithGrade = context.Students
                           .Where(s => s.FirstName == "Bill")
                           .Include(s => s.Grade)
                           .FirstOrDefault();

Trong ví dụ trên, .Include(s => s.Grade) truyền biểu thức lambda s => s.Grade để chỉ định một thuộc tính tham chiếu sẽ được tải với dữ liệu thực thể Student từ cơ sở dữ liệu trong một truy vấn SQL duy nhất.

Truy vấn trên thực hiện truy vấn SQL sau trong cơ sở dữ liệu.


SELECT TOP(1) [s].[StudentId], [s].[DoB], [s].[FirstName], [s].[GradeId],[s].[LastName], 
        [s].[MiddleName], [s.Grade].[GradeId], [s.Grade].[GradeName], [s.Grade].[Section]
FROM [Students] AS [s]
LEFT JOIN [Grades] AS [s.Grade] ON [s].[GradeId] = [s.Grade].[GradeId]
WHERE [s].[FirstName] = N'Bill'

Chúng ta cũng có thể chỉ định tên thuộc tính là một chuỗi trong phương thức Include(), giống như trong EF 6.


var context = new SchoolContext();

var studentWithGrade = context.Students
                              .Where(s => s.FirstName == "Bill")
                              .Include("Grade")
                              .FirstOrDefault();

Ví dụ trên không được khuyến nghị vì nó sẽ ném ngoại lệ lúc thực thi nếu tên thuộc tính bị sai chính tả hoặc không tồn tại.

Luôn sử dụng phương thức Include() với biểu thức lambda để có thể phát hiện lỗi trong thời gian biên dịch.

Phương thức mở rộng Include() cũng có thể được sử dụng sau phương thức FromSql() như ví dụ dưới đây.


var context = new SchoolContext();

var studentWithGrade = context.Students
    .FromSql("Select * from Students where FirstName ='Bill'")
    .Include(s => s.Grade)
    .FirstOrDefault();
Lưu ý: Phương thức mở rộng Include() không thể sử dụng sau phương thức DbSet.Find(). Ví dụ context.Students.Find(1).Include() là không thể trong EF Core 2.0. Điều này có thể có trong các phiên bản trong tương lai.

Nhiều phương thức Include trong EF Core

Sử dụng phương thức Include() nhiều lần để tải nhiều thuộc tính điều hướng của cùng một thực thể. Ví dụ, mã sau đây tải các thực thể liên quan GradeStudentCourses của thực thể Student.


var context = new SchoolContext();

var studentWithGrade = context.Students.Where(s => s.FirstName == "Bill")
                        .Include(s => s.Grade)
                        .Include(s => s.StudentCourses)
                        .FirstOrDefault();

Truy vấn trên sẽ thực hiện hai truy vấn SQL trong một lần gọi truy vấn cơ sở dữ liệu.


SELECT TOP(1) [s].[StudentId], [s].[DoB], [s].[FirstName], [s].[GradeId], [s].[LastName], 
        [s].[MiddleName], [s.Grade].[GradeId], [s.Grade].[GradeName], [s.Grade].[Section]
FROM [Students] AS [s]
LEFT JOIN [Grades] AS [s.Grade] ON [s].[GradeId] = [s.Grade].[GradeId]
WHERE [s].[FirstName] = N'Bill'
ORDER BY [s].[StudentId]
Go

SELECT [s.StudentCourses].[StudentId], [s.StudentCourses].[CourseId]
FROM [StudentCourses] AS [s.StudentCourses]
INNER JOIN (
    SELECT DISTINCT [t].*
    FROM (
        SELECT TOP(1) [s0].[StudentId]
        FROM [Students] AS [s0]
        LEFT JOIN [Grades] AS [s.Grade0] ON [s0].[GradeId] = [s.Grade0].[GradeId]
        WHERE [s0].[FirstName] = N'Bill'
        ORDER BY [s0].[StudentId]
    ) AS [t]
) AS [t0] ON [s.StudentCourses].[StudentId] = [t0].[StudentId]
ORDER BY [t0].[StudentId]
Go

Phương thức ThenInclude trong EF Core

EF Core đã giới thiệu phương thức mở rộng ThenInclude() mới để tải nhiều cấp độ của các thực thể liên quan. Hãy xem xét ví dụ sau:


var context = new SchoolContext();

var student = context.Students.Where(s => s.FirstName == "Bill")
                        .Include(s => s.Grade)
                            .ThenInclude(g => g.Teachers)
                        .FirstOrDefault();

Trong ví dụ trên, .Include(s => s.Grade) sẽ tải thuộc tính điều hướng tham chiếu Grade của thực thể Student. .ThenInclude(g => g.Teachers) sẽ tải thuộc tính danh sách Teacher của thực thể Grade.

Phương thức ThenInclude phải được gọi sau phương thức Include. Ví dụ ở trên sẽ thực hiện các truy vấn SQL sau trong cơ sở dữ liệu.


SELECT TOP(1) [s].[StudentId], [s].[DoB], [s].[FirstName], [s].[GradeId], [s].[LastName],
         [s].[MiddleName], [s.Grade].[GradeId], [s.Grade].[GradeName], [s.Grade].[Section]
FROM [Students] AS [s]
LEFT JOIN [Grades] AS [s.Grade] ON [s].[GradeId] = [s.Grade].[GradeId]
WHERE [s].[FirstName] = N'Bill'
ORDER BY [s.Grade].[GradeId]
Go

SELECT [s.Grade.Teachers].[TeacherId], [s.Grade.Teachers].[GradeId], [s.Grade.Teachers].[Name]
FROM [Teachers] AS [s.Grade.Teachers]
INNER JOIN (
    SELECT DISTINCT [t].*
    FROM (
        SELECT TOP(1) [s.Grade0].[GradeId]
        FROM [Students] AS [s0]
        LEFT JOIN [Grades] AS [s.Grade0] ON [s0].[GradeId] = [s.Grade0].[GradeId]
        WHERE [s0].[FirstName] = N'Bill'
        ORDER BY [s.Grade0].[GradeId]
    ) AS [t]
) AS [t0] ON [s.Grade.Teachers].[GradeId] = [t0].[GradeId]
ORDER BY [t0].[GradeId]
go

Truy vấn tạo kết quả đầu ra

Chúng tôi cũng có thể tải nhiều thực thể liên quan bằng cách sử dụng truy vấn tạo kết quả đầu ra thay vì sử dụng phương thức Include() hoặc ThenInclude().

Ví dụ sau đây cho thấy các truy vấn tạo kết quả đầu ra để tải các thực thể Student, GradeTeacher.


var context = new SchoolContext();

var stud = context.Students.Where(s => s.FirstName == "Bill")
                        .Select(s => new
                        {
                            Student = s,
                            Grade = s.Grade,
                            GradeTeachers = s.Grade.Teachers
                        })
                        .FirstOrDefault();

Trong ví dụ trên, phương thức mở rộng .Select được sử dụng để thêm các thực thể Student, GradeTeacher vào kết quả đầu ra. Điều này sẽ thực hiện cùng một truy vấn SQL như phương thức ThenInclude() ở trên.

Lazy loading trong EF Core

Lazy loading là gì?

Lazy loading trì hoãn việc tải các dữ liệu liên quan, cho đến khi bạn yêu cầu cụ thể. Lazy loading đối lập hoàn toàn với Eager loading.

Lazy loading được giới thiệu trong EF Core 2.1 như một tính năng tùy chọn bổ sung.

Lazy loading có thể được kích hoạt theo hai cách:

  • Sử dụng Proxies
  • Sử dụng interface ILazyLoader

Sử dụng Proxies

Proxies là các đối tượng có nguồn gốc từ các thực thể của bạn được Entity Framework Core tạo ra lúc thực thi.

Việc kích hoạt lazy loading bằng proxies yêu cầu ba bước:

Bước 1: Cài đặt gói Microsoft.EntityFrameworkCore.Proxies trong Package Manager Console như sau:

PM> install-package Microsoft.EntityFrameworkCore.Proxies

Nếu bạn sử dụng dotnet CLI, hãy nhập lệnh sau.

> add package Microsoft.EntityFrameworkCore.Proxies

Bước 2: Sử dụng phương thức UseLazyLoadingProxies để cho phép tạo proxies trong phương thức OnConfiguring của lớp DbContext:


protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder.UseLazyLoadingProxies();
}

Bước 3: Sử dụng từ khóa virtual cho tất cả các thuộc tính điều hướng như sau:


public class Student
{
    public Student() 
    {
        this.Courses = new HashSet<Course>();
    }

    public int StudentId { get; set; }
    [Required]
    public string StudentName { get; set; }

    public virtual ICollection<Course> Courses { get; set; }
}
        
public class Course
{
    public Course()
    {
        this.Students = new HashSet<Student>();
    }

    public int CourseId { get; set; }
    public string CourseName { get; set; }

    public virtual ICollection<Student> Students { get; set; }
}

Bước cuối cùng này là chìa khóa để cho phép EF Core ghi đè các thực thể của bạn để tạo proxies.

Ngoài ra, tất cả các kiểu thực thể phải là public, không được sử dụng từ khóa sealed và có một phương thức khởi tạo public hoặc protected.

Sử dụng interface ILazyLoader

Interface ILazyLoader đại diện cho một thành phần chịu trách nhiệm tải các thuộc tính điều hướng nếu chúng chưa được nạp.

Cách tiếp cận này hạn chế việc tạo ra các proxies không được hỗ trợ trên tất cả các nền tảng.

Interface ILazyLoader có thể được sử dụng theo một trong hai cách:

  • Nó có thể được inject vào thực thể chính trong mối quan hệ, nơi nó được sử dụng để tải người phụ thuộc. Điều này đòi hỏi các lớp mô hình của bạn có một sự phụ thuộc vào Microsoft.EntityFrameworkCore.Infrastructure, có sẵn trong gói Microsoft.EntityFrameworkCore.Abstractions.
  • Hoặc bạn có thể sử dụng một delegate dựa trên quy ước.

Các bước sau đây hướng dẫn cách sử dụng phương pháp đầu tiên:

Bước 1: Cài đặt gói Microsoft.EntityFrameworkCore.Abstrilities vào dự án chứa các lớp mô hình của bạn trong Package Manager Console như sau:

PM> install-package Microsoft.EntityFrameworkCore.Abstractions

Nếu bạn sử dụng dotnet CLI, hãy nhập lệnh sau.

> add package Microsoft.EntityFrameworkCore.Abstractions

Bước 2: Thực hiện các thay đổi cho thực thể chính như sau:

  • Thêm chỉ thị using Microsoft.EntityFrameworkCore.Infrastructure
  • Thêm một trường ILazyLoader.
  • Tạo một phương thức khởi tạo rỗng và một phương thức khởi tạo có tham số ILazyLoader.
  • Thêm một trường cho thuộc tính điều hướng danh sách.
  • Thêm một thuộc tính sử dụng phương thức ILazyLoader.Load trong get.

Ví dụ sau minh họa các thay đổi trong bước 2:


using Microsoft.EntityFrameworkCore.Infrastructure;

public class Student
{
    private List<Course> _courses;
    private readonly ILazyLoader _lazyLoader;
	
    public Student()
    {
    }
    public Student(ILazyLoader lazyLoader)
    {
        _lazyLoader = lazyLoader;
    }

    public int StudentId { get; set; }
    [Required]
    public string StudentName { get; set; }

    public List<Course> Courses
    {
        get => _lazyLoader.Load(this, ref _courses);
        set => _courses = value;
    }
}

Explicit Loading trong EF Core

Ở phần này bạn sẽ tìm hiểu làm thế nào để tải các thực thể liên quan trong một biểu đồ thực thể rõ ràng.

Sử dụng phương thức Load() để tải các thực thể liên quan một cách rõ ràng. Hãy xem xét ví dụ sau.

using (var context = new SchoolContext())
{
    var student = context.Students
                         .Where(s => s.FirstName == "Bill")
                         .FirstOrDefault<Student>();
    
    // loads StudentAddress
    context.Entry(student).Reference(s => s.StudentAddress).Load(); 
    
    // loads Courses collection 
    context.Entry(student).Collection(s => s.StudentCourses).Load();
}

Trong ví dụ trên, câu lệnh context.Entry(student).Reference(s => s.StudentAddress).Load() sẽ tải thực thể StudentAddress. Phương thức Reference() được sử dụng để lấy một đối tượng của thuộc tính điều hướng tham chiếu được chỉ định và các phương thức Load() tải nó một cách rõ ràng.

Theo cùng một cách, câu lệnh context.Entry(student).Collection(s => s.Courses).Load() tải thuộc tính điều hướng danh sách Courses của thực thể Student. Phương thức Collection() nhận một đối tượng đại diện cho thuộc tính điều hướng danh sách.

Phương thức Load() thực hiện truy vấn SQL trong cơ sở dữ liệu để lấy dữ liệu và lấp đầy các tham chiếu được chỉ định hoặc thuộc tính danh sách trong bộ nhớ, như hình dưới đây.

Explicit loading trong Entity Framework

Phương thức Query

Bạn cũng có thể viết các truy vấn LINQ-to-Entities để lọc dữ liệu liên quan trước khi tải. Phương thức Query() cho phép chúng ta viết các truy vấn LINQ thêm cho các đối tượng có liên quan để lọc ra dữ liệu có liên quan.

using (var context = new SchoolContext())
{
    var student = context.Students
                         .Where(s => s.FirstName == "Bill")
                         .FirstOrDefault<Student>();
    
    context.Entry(student)
           .Collection(s => s.StudentCourses)
           .Query()
               .Where(sc => sc.CourseName == "Maths")
               .FirstOrDefault();
}

Trong ví dụ trên, biểu thức .Collection(s => s.StudentCourses).Query() cho phép chúng tôi viết các truy vấn tiếp theo cho thực thể StudentCourses.



Bài viết liên quan:

2 kịch bản lưu dữ liệu trong Entity Framework Core là kịch bản được kết nối và kịch bản ngắt kết nối.

Tạo ứng dụng .NET Core Console đầu tiên và cấu hình sử dụng Entity Framework Core.

Entity Framework Core toàn tập sẽ hướng dẫn bạn tất cả mọi thứ về Entity Framework Core.