LINQ toàn tập

Giới thiệu về LINQ

LINQ (truy vấn tích hợp ngôn ngữ) là ngôn ngữ truy vấn mạnh mẽ được giới thiệu trong .NET Framework 3.5 & Visual Studio 2008. LINQ có thể được sử dụng với C# hoặc VB.NET để truy vấn các nguồn dữ liệu khác nhau.

Hướng dẫn LINQ toàn tập này sẽ giúp bạn học LINQ bằng các chủ đề từ cơ bản đến nâng cao. Hướng dẫn này được chia thành nhiều chủ đề liên quan, để bạn bắt đầu từ một chủ đề phải được hiểu trước, sau đó dần dần tìm hiểu các tính năng khác của LINQ một cách tuần tự.

Các hướng dẫn LINQ được trình bày khoa học với các giải thích dễ hiểu, các ví dụ thực tế, các mẹo hữu ích, ghi chú thông tin và các điểm cần nhớ.

Đối tượng độc giả

Các hướng dẫn này được thiết kế cho người mới bắt đầu và các chuyên gia muốn tìm hiểu LINQ từng bước.

Điều kiện tiên quyết

Kiến thức cơ bản về .NET Framework3.5 / 4.5, lập trình C#, Visual Studio là bắt buộc.

LINQ là gì?

LINQ (Truy vấn tích hợp ngôn ngữ) là cú pháp truy vấn thống nhất trong C# và VB.NET để truy xuất dữ liệu từ các nguồn và định dạng khác nhau.

Nó được tích hợp trong C# hoặc VB.NET, do đó loại bỏ sự không phù hợp giữa các ngôn ngữ lập trình và cơ sở dữ liệu, cũng như cung cấp một giao diện truy vấn duy nhất cho các loại nguồn dữ liệu khác nhau.

Ví dụ: SQL là ngôn ngữ truy vấn có cấu trúc được sử dụng để lưu và truy xuất dữ liệu từ cơ sở dữ liệu. Theo cùng một cách, LINQ là một cú pháp truy vấn có cấu trúc được xây dựng trong C# và VB.NET để truy xuất dữ liệu từ các loại nguồn dữ liệu khác nhau, chẳng hạn như danh sách, ADO.Net DataSet, XML Docs, web service, MS SQL Server và các cơ sở dữ liệu khác.

LINQ là gì?

Truy vấn LINQ trả về kết quả dưới dạng đối tượng. Nó cho phép bạn sử dụng cách tiếp cận hướng đối tượng trên tập kết quả và không phải lo lắng về việc chuyển đổi các định dạng khác nhau của kết quả thành các đối tượng.

 LINQ là gì?

Ví dụ sau đây minh họa một truy vấn LINQ đơn giản trả về tất cả các chuỗi có chứa ký tự 'a' trong một mảng.

// Data source
string[] names = { "Bill", "Steve", "James", "Mohan" };

// LINQ Query 
var myLinqQuery = from name in names
                  where name.Contains('a')
                  select name;
    
// Query execution
foreach(var name in myLinqQuery)
{
    Console.WriteLine(name);
}

Kết quả khi biên dịch và thực thi chương trình trên:

James
Mohan

Trong ví dụ trên, mảng chuỗi là nguồn dữ liệu. Sau đây là truy vấn LINQ được gán cho biến myLinqQuery.

from name in names
where name.Contains('a')
select name;

Truy vấn trên sử dụng cú pháp truy vấn của LINQ. Bạn sẽ tìm hiểu thêm về nó trong chương Cú pháp truy vấn LINQ.

Cú pháp truy vấn LINQ | Comdy
Cú pháp biểu thức truy vấn LINQ và cú pháp phương thức truy vấn LINQ.

Bạn sẽ không nhận được kết quả của truy vấn LINQ cho đến khi bạn thực thi nó. Truy vấn LINQ có thể được thực thi theo nhiều cách, ở đây chúng tôi đã sử dụng vòng lặp foreach để thực hiện truy vấn được lưu trữ trong biến myLinqQuery. Vòng lặp foreach sẽ thực hiện truy vấn trên nguồn dữ liệu và nhận được kết quả và sau đó duyệt qua tập kết quả.

Như vậy, LINQ có thể truy vấn trên nhiều loại nguồn dữ liệu khác nhau. Sau khi viết truy vấn LINQ, nó phải được thực thi để có kết quả.

Tại sao lại là LINQ?

Để hiểu lý do tại sao chúng ta nên sử dụng LINQ, hãy xem xét một số ví dụ sau. Giả sử bạn muốn tìm những sinh viên tuổi từ 13 đến 19 từ một danh sách sinh viên.

Trước C# 2.0, chúng tôi đã phải sử dụng vòng lặp foreach hoặc for duyệt qua danh sách để tìm những sinh viên thỏa điều kiện như dưới đây:

class Student
{
    public int StudentID { get; set; }
    public String StudentName { get; set; }
    public int Age { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        Student[] studentArray = 
        { 
            new Student() { StudentID = 1, StudentName = "John", Age = 18 },
            new Student() { StudentID = 2, StudentName = "Steve",  Age = 21 },
            new Student() { StudentID = 3, StudentName = "Bill",  Age = 25 },
            new Student() { StudentID = 4, StudentName = "Ram" , Age = 20 },
            new Student() { StudentID = 5, StudentName = "Ron" , Age = 31 },
            new Student() { StudentID = 6, StudentName = "Chris",  Age = 17 },
            new Student() { StudentID = 7, StudentName = "Rob",Age = 19  },
        };

        var students = new List<Student>();
        foreach (Student student in studentArray)
        {
            if (student.Age > 12 && student.Age < 20)
            {
                students.Add(student);
            }
        }
    }
}

Sử dụng vòng lặp foreachfor khá là cồng kềnh, khó bảo trì và khó đọc. Delegate được giới thiệu từ C# 2.0, có thể được sử dụng để giải quyết loại kịch bản này, như được trình bày dưới đây:

delegate bool FindStudent(Student student);

class StudentExtension
{ 
    public static List<Student> Where(Student[] stdArray, FindStudent findStudent)
    {
        List<Student> result = new List<Student>();
        foreach (Student student in stdArray)
        {
            if (findStudent(student))
            {
                result.Add(student);
            }
        }        
        return result;
    }
}
    
class Program
{
    static void Main(string[] args)
    {
        Student[] studentArray = 
        { 
            new Student() { StudentID = 1, StudentName = "John", Age = 18 },
            new Student() { StudentID = 2, StudentName = "Steve",  Age = 21 },
            new Student() { StudentID = 3, StudentName = "Bill",  Age = 25 },
            new Student() { StudentID = 4, StudentName = "Ram" , Age = 20 },
            new Student() { StudentID = 5, StudentName = "Ron" , Age = 31 },
            new Student() { StudentID = 6, StudentName = "Chris",  Age = 17 },
            new Student() { StudentID = 7, StudentName = "Rob",Age = 19  },
        };

        var students = StudentExtension.Where(studentArray, delegate(Student student) 
        {
            return student.Age > 12 && student.Age < 20;
        });
    }
}

Vì vậy, với ưu điểm của delegate bạn có thể tìm kiếm sinh viên với bất kỳ tiêu chí nào. Bạn không phải viết lại các vòng lặp foreach hoặc for để tìm sinh viên sử dụng các tiêu chí khác nhau.

Ví dụ: bạn có thể sử dụng cùng chức năng delegate để tìm một sinh viên có StudentId là 5 hoặc tên là Bill, như sau:

var students = StudentExtension.Where(studentArray, delegate(Student std) {
    return std.StudentID == 5;
});

//Also, use another criteria using same delegate
var students = StudentExtension.Where(studentArray, delegate(Student std) {
    return std.StudentName == "Bill";
});

Nhóm phát triển C# cảm thấy rằng họ vẫn cần làm cho mã nhỏ gọn và dễ đọc hơn. Vì vậy, họ đã giới thiệu phương thức mở rộng, biểu thức lambda, cây biểu thức, kiểu ẩn danh và biểu thức truy vấn trong C# 3.0.

Bạn có thể sử dụng các tính năng này của C# 3.0, chúng là các khối thành phần tạo nên LINQ. Chúng có thể được sử dụng để truy vấn các loại danh sách khác nhau và nhận kết quả trả về trong một câu lệnh.

Ví dụ dưới đây cho thấy cách bạn có thể sử dụng truy vấn LINQ với biểu thức lambda để tìm sinh viên như ví dụ ở bên trên.

class Program
{
    static void Main(string[] args)
    {
        Student[] studentArray = 
        { 
			new Student() { StudentID = 1, StudentName = "John", age = 18 },
			new Student() { StudentID = 2, StudentName = "Steve",  age = 21 },
			new Student() { StudentID = 3, StudentName = "Bill",  age = 25 },
			new Student() { StudentID = 4, StudentName = "Ram" , age = 20 },
			new Student() { StudentID = 5, StudentName = "Ron" , age = 31 },
			new Student() { StudentID = 6, StudentName = "Chris",  age = 17 },
			new Student() { StudentID = 7, StudentName = "Rob",age = 19  },
		};

        // Use LINQ to find teenager students
        var teenAgerStudents = studentArray.Where(s => s.age > 12 && s.age < 20).ToList();
       
        // Use LINQ to find first student whose name is Bill 
        var bill = studentArray.Where(s => s.StudentName == "Bill").FirstOrDefault();
        
        // Use LINQ to find student whose StudentID is 5
        var student5 = studentArray.Where(s => s.StudentID == 5).FirstOrDefault();
    }
}

Như bạn có thể thấy trong ví dụ trên, chúng tôi chỉ định các tiêu chí khác nhau bằng cách sử dụng toán tử LINQ và biểu thức lambda trong một câu lệnh.

Do đó, LINQ làm cho mã nhỏ gọn hơn và dễ đọc hơn và nó cũng có thể được sử dụng để truy vấn các nguồn dữ liệu khác nhau.

Ví dụ: nếu bạn có một bảng sinh viên trong cơ sở dữ liệu thay vì một mảng các đối tượng sinh viên như trên, bạn vẫn có thể sử dụng cùng một truy vấn để tìm sinh viên bằng cách sử dụng Entity Framework.

Ưu điểm của LINQ

  • Ngôn ngữ quen thuộc: Nhà phát triển không phải học ngôn ngữ truy vấn mới cho từng loại nguồn dữ liệu hoặc định dạng dữ liệu.
  • Viết ít code: Nó làm giảm số lượng mã được viết so với cách tiếp cận truyền thống hơn.
  • Code dễ đọc: LINQ làm cho mã dễ đọc hơn để các nhà phát triển khác có thể dễ dàng hiểu và bảo trì nó.
  • Cách truy vấn chuẩn hóa nhiều nguồn dữ liệu: Có thể sử dụng cùng một cú pháp LINQ để truy vấn nhiều nguồn dữ liệu.
  • An toàn khi biên dịch của các truy vấn: Nó cung cấp kiểm tra kiểu của các đối tượng tại thời gian biên dịch.
  • Hỗ trợ IntelliSense: LINQ cung cấp IntelliSense cho các danh sách kiểu generic.
  • Định hình dữ liệu: Bạn có thể truy xuất dữ liệu theo các hình dạng khác nhau.

LINQ API

Chúng ta có thể viết các truy vấn LINQ cho các lớp triển khai interface IEnumerable<T> hoặc IQueryable<T>. Namespace System.Linq bao gồm các lớp và interface sau đây được yêu cầu cho các truy vấn LINQ.

LINQ class diagram

Các truy vấn LINQ sử dụng các phương thức mở rộng cho các lớp triển khai interface IEnumerable hoặc IQueryable. Hai lớp static EnumerableQueryable có chứa các phương thức mở rộng để viết các truy vấn LINQ.

Tips: using System.Linq; đã được thêm vào khai báo lớp theo mặc định khi bạn thêm một lớp mới trong Visual Studio.

Enumerable

Lớp Enumerable bao gồm các phương thức mở rộng cho các lớp triển khai interface IEnumerable<T>, ví dụ như tất cả các kiểu collection generic được tích hợp sẵn trong C# đều triển khai interface IEnumerable<T> và vì vậy chúng ta có thể viết các truy vấn LINQ để lấy dữ liệu từ các collection tích hợp này.

Hình dưới đây cho thấy các phương thức mở rộng có trong lớp Enumerable có thể được sử dụng với các collection generic trong C# hoặc VB.Net.

Các phương thức mở rộng của lớp Enumerable

Hình dưới đây cho thấy tất cả các phương thức mở rộng có sẵn trong lớp Enumerable.

Tất cả các phương thức mở rộng có sẵn trong lớp Enumerable

Queryable

Lớp Queryable bao gồm các phương thức mở rộng cho các lớp triển khai interface IQueryable<T>. Interface IQueryable<T> được sử dụng để cung cấp khả năng truy vấn đối với một nguồn dữ liệu cụ thể mà các kiểu dữ liệu đã được biết đến. Ví dụ, các API của Entity Framework triển khai interface IQueryable<T> để hỗ trợ các truy vấn LINQ với cơ sở dữ liệu bên dưới như MS SQL Server.

Ngoài ra, còn có các API có sẵn để truy cập dữ liệu của bên thứ ba; ví dụ: LINQ to Amazon cung cấp khả năng sử dụng LINQ với các web service của Amazon để tìm kiếm sách và các mặt hàng khác. Điều này có thể đạt được bằng cách triển khai interface IQueryable cho Amazon.

Hình dưới đây cho thấy các phương thức mở rộng có sẵn trong lớp Queryable có thể được sử dụng với các nhà cung cấp dữ liệu gốc hoặc bên thứ ba khác nhau.

Các phương thức mở rộng của lớp Queryable

Hình dưới đây cho thấy tất cả các phương thức mở rộng có sẵn trong lớp Queryable.

Tất cả các phương thức mở rộng có sẵn trong lớp Queryable

Những điểm cần nhớ:

  1. Sử dụng namespace System.Linq để sử dụng LINQ.
  2. LINQ API bao gồm hai lớp tĩnh chính Enumerable và Queryable.
  3. Lớp Enumerable tĩnh bao gồm các phương thức mở rộng cho các lớp triển khai interface IEnumerable<T>.
  4. Kiểu IEnumerable<T> là collection trong bộ nhớ như List<T>, Dictionary<TKey, TValue>, SortedList<TKey, TValue>, Queue<T>, Stack<T>, Hashset<T>.
  5. Lớp Queryable tĩnh bao gồm các phương thức mở rộng cho các lớp triển khai interface IQueryable<T>.

Cú pháp viết truy vấn LINQ

Có hai cách cơ bản để viết truy vấn LINQ cho danh sách IEnumerable hoặc nguồn dữ liệu IQueryable.

  1. Cú pháp truy vấn LINQ.
  2. Cú pháp phương thức LINQ.

Cú pháp truy vấn LINQ

Cú pháp truy vấn tương tự như truy vấn SQL (Ngôn ngữ truy vấn có cấu trúc) cho cơ sở dữ liệu. Nó có cú pháp trong mã C# hoặc VB.NET như sau:

from <range variable> in <IEnumerable<T> or IQueryable<T> Collection>

<Standard Query Operators> <lambda expression>

<select or groupBy operator> <result formation>

Cú pháp truy vấn LINQ bắt đầu bằng từ khóa from và kết thúc bằng từ khóa select. Điều này ngược với cú pháp truy vấn SQL bắt đầu bằng từ khóa select.

Lý do bắt đầu bằng từ khóa from là IntelliSense sẽ biết trước được kiểu dữ liệu để hỗ trợ bạn gõ biểu thức truy vấn nhanh chóng.

Ví dụ dưới đây minh họa một biểu thức truy vấn LINQ trả về các chuỗi có chứa từ "Tutorials":

// string collection
IList<string> stringList = new List<string>() 
{ 
    "C# Tutorials",
    "VB.NET Tutorials",
    "Learn C++",
    "MVC Tutorials" ,
    "Java" 
};

// LINQ Query Syntax
var result = from s in stringList
             where s.Contains("Tutorials") 
             select s;

foreach(var item in result)
{
    Console.WriteLine(item);
}

Đây là kết quả khi biên dịch và thực thi chương trình trên:

C# Tutorials
VB.NET Tutorials
MVC Tutorials

Hình dưới đây cho thấy cấu trúc của cú pháp truy vấn LINQ.

Cú pháp biểu thức truy vấn LINQ

Cú pháp phương thức LINQ

Cú pháp phương thức LINQ sử dụng các phương thức mở rộng có trong hai lớp tĩnh là Enumerable hoặc Queryable, tương tự như cách bạn sẽ gọi các phương thức mở rộng của một lớp bất kỳ.

Tips: Trình biên dịch sẽ chuyển đổi cú pháp truy vấn LINQ thành cú pháp phương thức LINQ tại thời điểm biên dịch.

Ví dụ dưới đây minh họa một truy vấn sử dụng cú pháp phương thức LINQ trả về những chuỗi có chứa một từ "Tutorials".

// string collection
IList<string> stringList = new List<string>() 
{ 
    "C# Tutorials",
    "VB.NET Tutorials",
    "Learn C++",
    "MVC Tutorials" ,
    "Java" 
};

// LINQ Query Syntax
var result = stringList.Where(s => s.Contains("Tutorials")).ToList();
foreach(var item in result)
{
    Console.WriteLine(item);
}

Đây là kết quả khi biên dịch và thực thi chương trình trên:

C# Tutorials
VB.NET Tutorials
MVC Tutorials

Hình dưới đây minh họa cấu trúc của cú pháp phương thức LINQ.

Cú pháp phương thức truy vấn LINQ

Bạn có thể xem chi tiết về cú pháp truy vấn LINQ trong bài viết sau:

Cú pháp truy vấn LINQ | Comdy
Tìm hiểu hai cú pháp truy vấn LINQ: Cú pháp truy vấn LINQ và cú pháp phương thức LINQ.

Biểu thức Lambda

C# 3.0 (.NET Framework 3.5) đã giới thiệu biểu thức lambda cùng với LINQ. Biểu thức lambda là một cách ngắn hơn để biểu diễn phương thức ẩn danh bằng cách sử dụng một số cú pháp đặc biệt.

Ví dụ: phương thức ẩn danh sau kiểm tra nếu sinh viên là thiếu niên hay không (độ tuổi từ 13 đến 19):

delegate(Student s) { return s.Age > 12 && s.Age < 20; };

Phương thức ẩn danh ở trên có thể được biểu diễn bằng biểu thức Lambda trong C# như sau:

s => s.Age > 12 && s.Age < 20

Chúng ta hãy xem cách biểu thức lambda tiến hóa từ phương thức ẩn danh sau:

delegate(Student s) { return s.Age > 12 && s.Age < 20; };

Biểu thức Lambda tiến hóa từ phương thức ẩn danh bằng cách loại bỏ từ khóa delegate và kiểu dữ liệu của tham số rồi thêm toán tử lambda =>.

Biểu thức Lambda

Biểu thức lambda ở trên là hoàn toàn hợp lệ, nhưng chúng ta không cần dấu ngoặc nhọn, return và dấu chấm phẩy nếu chúng ta chỉ có một câu lệnh trả về giá trị. Vì vậy, chúng ta có thể loại bỏ nó.

Ngoài ra, chúng ta có thể loại bỏ dấu ngoặc đơn (), nếu chúng ta chỉ có một tham số.

Biểu thức Lambda

Do đó, chúng ta có biểu thức lambda: s => s.Age > 12 && s.Age < 20 trong đó s là một tham số, => là toán tử lambda và s.Age > 12 && s.Age < 20 là thân của biểu thức:

Biểu thức Lambda

Bạn có thể xem chi tiết về biểu thức Lambda ở bài viết sau:

Biểu thức Lambda | Comdy
Biểu thức Lambda là gì? Làm sao để khai báo và sử dụng biểu thức Lambda.

Toán tử truy vấn chuẩn của LINQ

Toán tử truy vấn chuẩn là gì?

Các toán tử truy vấn chuẩn trong LINQ thực sự là các phương thức mở rộng cho các kiểu IEnumerable<T>IQueryable<T>. Chúng được định nghĩa trong hai lớp System.Linq.EnumerableSystem.Linq.Queryable.

Có hơn 50 toán tử truy vấn tiêu chuẩn có sẵn trong LINQ cung cấp các chức năng khác nhau như lọc, sắp xếp, gom nhóm, tổng hợp, nối, v.v.

Toán tử truy vấn chuẩn trong cú pháp truy vấn LINQ

Cú pháp truy vấn LINQ

Toán tử truy vấn chuẩn trong cú pháp phương thức LINQ

Cú pháp phương thức LINQ

Các toán tử truy vấn chuẩn trong cú pháp biểu thức truy vấn sẽ được chuyển đổi thành các phương thức mở rộng lúc biên dịch. Vì vậy, cả hai đều giống nhau.

Phân loại toán tử truy vấn chuẩn trong LINQ

Toán tử truy vấn tiêu chuẩn có thể được phân loại dựa trên chức năng chúng cung cấp. Bảng sau liệt kê tất cả các phân loại của toán tử truy vấn chuẩn:

Tips: bạn hãy click vào tên loại toán tử ở bảng bên dưới để xem chi tiết cách sử dụng nhé.
Phân loại Các toán tử truy vấn chuẩn
Lọc Where, OfType
Sắp xếp OrderBy, OrderByDescending, ThenBy, ThenByDescending, Reverse
Nhóm GroupBy, ToLookup
Join GroupJoin, Join
Projection Select, SelectMany
Tổng hợp Aggregate, Average, Count, LongCount, Max, Min, Sum
Định lượng All, Any, Contains
Phần tử ElementAt, ElementAtOrDefault, First, FirstOrDefault, Last, LastOrDefault, Single, SingleOrDefault
Tập hợp Distinct, Except, Intersect, Union
Phân vùng Skip, SkipWhile, Take, TakeWhile
Liên kết Concat
So sánh bằng SequenceEqual
Tạo danh sách DefaultEmpty, Empty, Range, Repeat
Chuyển đổi AsEnumerable, AsQueryable, Cast, ToArray, ToDictionary, ToList

Expression trong LINQ

Chúng ta đã tìm hiểu về Biểu thức lambda có thể gán cho kiểu delegate Func hoặc delegate Action để xử lý các tập hợp trong bộ nhớ. Trình biên dịch .NET sẽ chuyển đổi biểu thức lambda được gán cho delegate Func hoặc delegate Action thành mã thực thi tại thời điểm biên dịch.

Expression trong LINQ là gì?

LINQ đã giới thiệu kiểu dữ liệu mới có tên là Expression (biểu thức) đại diện cho biểu thức lambda được định kiểu mạnh. Nó có nghĩa là biểu thức lambda cũng có thể được gán cho kiểu Expression<TDelegate>.

Trình biên dịch .NET chuyển đổi biểu thức lambda được gán cho Expression<TDelegate> thành cây biểu thức thay vì mã thực thi.

Cây biểu thức (expression tree) này được các trình cung cấp truy vấn LINQ từ xa (remote LINQ query providers) sử dụng làm cấu trúc dữ liệu để xây dựng một truy vấn lúc thực thi từ nó (như LINQ-to-SQL, EntityFramework hoặc bất kỳ trình cung cấp truy vấn LINQ nào khác triển khai interface IQueryable<T>).

Hình dưới đây minh họa sự khác biệt khi biểu thức lambda được gán cho delegate Func hoặc delegate Action và Expression trong LINQ.

Expression trong LINQ là gì?

Bạn có thể xem chi tiết về Expression trong LINQ ở bài viết sau:

Expression trong LINQ | Comdy
Expression trong LINQ là gì? Cây biểu thức trong LINQ là gì? Cách khai báo và sử dụng chúng trong LINQ.

Thực thi truy vấn LINQ

Có hai kịch bản thực thi truy vấn LINQ:

  • Trì hoãn thực thi truy vấn LINQ (Deferred Execution of LINQ Query).
  • Thực thi ngay lập tức truy vấn LINQ (Immediate Execution of LINQ Query).

Trì hoãn thực thi truy vấn LINQ

Trì hoãn thực thi là gì?

Trì hoãn thực thi (Deferred Execution) có nghĩa là việc đánh giá một biểu thức bị trì hoãn cho đến khi giá trị thực sự của nó được yêu cầu.

Trì hoãn thực thi truy vấn LINQ giúp cải thiện đáng kể hiệu suất bằng cách tránh thực thi không cần thiết.

Việc trì hoãn thực thi được áp dụng trên mọi kiểu tập hợp trong bộ nhớ cũng như các trình cung cấp LINQ từ xa như LINQ-to-SQL, LINQ-to-Entities, LINQ-to-XML, v.v.

Hình ảnh đưới đây minh họa trì hoãn thực thi truy vấn LINQ:

Trì hoãn thực thi truy vấn LINQ

Trong ví dụ trên, bạn có thể thấy truy vấn được thực thi khi bạn thực hiện duyệt danh sách bằng vòng lặp foreach.

Điều này được gọi là trì hoãn thực thi.

LINQ xử lý danh sách studentList khi bạn thực sự truy cập từng đối tượng trong danh sách và làm một cái gì đó với nó.

Thực thi ngay lập tức truy vấn LINQ

Thực hiện ngay lập tức thì trái ngược với trì hoãn thực thì. Nó buộc truy vấn LINQ thực thi và nhận kết quả ngay lập tức. Các toán tử chuyển đổi To thực hiện truy vấn đã cho và đưa ra kết quả ngay lập tức.

Cú pháp phương thức

Trong ví dụ sau, phương thức mở rộng ToList() thực hiện truy vấn ngay lập tức và trả về kết quả.

IList<Student> teenAgerStudents = studentList
    .Where(s => s.age > 12 && s.age < 20)
    .ToList();

Cú pháp truy vấn

var teenAgerStudents = from s in studentList
                       where s.age > 12 && s.age < 20
                       select s;

Các truy vấn trên sẽ không thực hiện ngay lập tức. Bạn sẽ không tìm thấy bất kỳ kết quả nào như hình dưới đây:

Thực thi ngay lập tức truy vấn LINQ

Cú pháp truy vấn không hỗ trợ toán tử 'To' nhưng có thể sử dụng phương thức ToList(), ToArray() hoặc ToDictionary() để thực thi ngay lập tức như dưới đây:

IList<Student> teenAgerStudents = (from s in studentList
                                   where s.age > 12 && s.age < 20
                                   select s).ToList();

Bạn có thể xem kết quả trong danh sách teenAgerStudents như sau:

Thực thi ngay lập tức truy vấn LINQ

Bạn có thể xem chi tiết về thực thi truy vấn LINQ ở bài viết sau:

Thực thi truy vấn LINQ | Comdy
Trì hoãn thực thi truy vấn LINQ là gì? Thực thi ngay lập tức truy vấn LINQ là gì? Làm sao để thực thi truy vấn LINQ.

Từ khóa let, into trong LINQ

Từ khóa let trong LINQ

Từ khóa let rất hữu ích trong cú pháp truy vấn. Nó tạo ra một biến phạm vi mới, cho phép sử dụng lại biểu thức và làm cho truy vấn dễ đọc hơn.

Ví dụ: bạn có thể so sánh các giá trị chuỗi và chọn giá trị chuỗi chữ thường như dưới đây:

IList<Student> studentList = new List<Student>() 
{ 
    new Student() { StudentID = 1, StudentName = "John", Age = 13 },
    new Student() { StudentID = 2, StudentName = "Steve", Age = 15 },
    new Student() { StudentID = 3, StudentName = "Bill", Age = 18 },
    new Student() { StudentID = 4, StudentName = "Ram", Age = 12 },
    new Student() { StudentID = 5, StudentName = "Ron", Age = 21 } 
};
            
var lowercaseStudentNames = from s in studentList
                            where s.StudentName.ToLower().StartsWith("r")
                            select s.StudentName.ToLower();

Như bạn có thể thấy, phương thức ToLower() được sử dụng nhiều lần trong truy vấn trên.

Ví dụ sau sử dụng từ khóa let để tạo biến mới có tên 'lowcaseStudentName' để lưu trữ tạm thời tên viết thường của sinh viên, sau đó có thể sử dụng biến này ở bất cứ nơi nào.

IList<Student> studentList = new List<Student>() 
{ 
    new Student() { StudentID = 1, StudentName = "John", Age = 13 },
    new Student() { StudentID = 2, StudentName = "Steve", Age = 15 },
    new Student() { StudentID = 3, StudentName = "Bill", Age = 18 },
    new Student() { StudentID = 4, StudentName = "Ram", Age = 12 },
    new Student() { StudentID = 5, StudentName = "Ron", Age = 21 } 
};
            
var lowercaseStudentNames = from s in studentList
                            let lowercaseStudentName = s.StudentName.ToLower()
                            where lowercaseStudentName.StartsWith("r")
                            select lowercaseStudentName;

foreach (var name in lowercaseStudentNames)
{
    Console.WriteLine(name);
}

Đây là kết quả khi biên dịch và thực thi chương trình:

ram
ron

Vì vậy, từ khóa let được sử dụng để làm cho truy vấn dễ đọc hơn.

Từ khóa into trong LINQ

Từ khóa into được sử dụng để tiếp tục truy vấn sau mệnh đề select. Ví dụ dưới đây minh họa sử dụng từ khóa into trong LINQ:

IList<Student> studentList = new List<Student>() 
{ 
    new Student() { StudentID = 1, StudentName = "John", Age = 13 },
    new Student() { StudentID = 2, StudentName = "Steve", Age = 15 },
    new Student() { StudentID = 3, StudentName = "Bill", Age = 18 },
    new Student() { StudentID = 4, StudentName = "Ram", Age = 12 },
    new Student() { StudentID = 5, StudentName = "Ron", Age = 21 } 
};

var teenAgerStudents = from s in studentList
                       where s.Age > 12 && s.Age < 20
                       select s
                            into teenStudent
                            where teenStudent.StudentName.StartsWith("B")
                            select teenStudent;
                            
foreach (var student in teenAgerStudents)
{
    Console.WriteLine("Name: {0} - Age: {1}", student.StudentName, student.Age);
}

Đây là kết quả khi biên dịch và thực thi chương trình:

Name: Bill - Age: 18

Trong ví dụ trên, từ khóa into đã tạo một biến phạm vi mới tên là teenStudent. Do biến phạm vi đầu tiên s đã nằm ngoài phạm vi nên bạn có thể viết thêm truy vấn sau từ khóa into sử dụng biến phạm vi mới.

Các truy vấn LINQ phức tạp

Trong phần này, bạn sẽ tìm hiểu một số truy vấn LINQ phức tạp. Chúng tôi sẽ sử dụng danh sách Student và Standard dưới đây cho các truy vấn của chúng tôi.

IList<Student> studentList = new List<Student>() 
{ 
    new Student() { StudentID = 1, StudentName = "John", Age = 18, StandardID = 1 },
    new Student() { StudentID = 2, StudentName = "Steve", Age = 21, StandardID = 1 },
    new Student() { StudentID = 3, StudentName = "Bill", Age = 18, StandardID = 2 },
    new Student() { StudentID = 4, StudentName = "Ram", Age = 20, StandardID = 2 },
    new Student() { StudentID = 5, StudentName = "Ron", Age = 21 } 
};

IList<Standard> standardList = new List<Standard>() 
{ 
    new Standard(){ StandardID = 1, StandardName="Standard 1"},
    new Standard(){ StandardID = 2, StandardName="Standard 2"},
    new Standard(){ StandardID = 3, StandardName="Standard 3"}
};

Nhiều toán tử Select và Where

var studentNames = studentList.Where(s => s.Age > 18)
                              .Select(s => s)
                              .Where(st => st.StandardID > 0)
                              .Select(s => s.StudentName);

Đây là kết quả khi biên dịch và thực thi:

Steve
Ram

Truy vấn sau đây trả về danh sách các đối tượng ẩn danh chỉ có thuộc tính StudentName:

var teenStudentsName = from s in studentList
                       where s.age > 12 && s.age < 20
                       select new { StudentName = s.StudentName };

teenStudentsName.ToList().ForEach(s => Console.WriteLine(s.StudentName));

Đây là kết quả khi biên dịch và thực thi:

John
Bill

Group By

Truy vấn sau đây trả về danh sách nhóm sinh viên theo StandardID:

var studentsGroupByStandard = from s in studentList
                              group s by s.StandardID into sg
                              orderby sg.Key 
                                    select new { sg.Key, sg };


foreach (var group in studentsGroupByStandard)
{
    Console.WriteLine("StandardID {0}:", group.Key);    
    group.sg.ToList().ForEach(st => Console.WriteLine(st.StudentName ));
}

Đây là kết quả khi biên dịch và thực thi:

StandardID 0:
Ron
StandardID 1:
John
Steve
StandardID 2:
Bill
Ram

Đầu ra bao gồm Ron, người không có StandardID. Vì vậy, Ron mặc định có StandardID 0.

Để xóa một học sinh không có StandardID, hãy sử dụng toán tử where trước toán tử nhóm:

var studentsGroupByStandard = from s in studentList
                              where s.StandardID > 0
                              group s by s.StandardID into sg
                              orderby sg.Key 
                                    select new { sg.Key, sg };

Đây là kết quả khi biên dịch và thực thi:

StandardID 1:
John
Steve
StandardID 2:
Bill
Ram

Left Join

Sử dụng Left Join để hiển thị Student theo từng Standard. Hiển thị tên Standard ngay cả khi không có Student nào được gán cho Standard đó.

var studentsGroup = from stad in standardList
                    join s in studentList
                    on stad.StandardID equals s.StandardID
                        into sg
                        select new 
                        { 
                            StandardName = stad.StandardName, 
                            Students = sg 
                        };

foreach (var group in studentsGroup)
{
    Console.WriteLine(group.StandardName);    
    group.Students.ToList().ForEach(st => Console.WriteLine(st.StudentName));
}

Đây là kết quả khi biên dịch và thực thi:

Standard 1
John
Steve
Standard 2
Bill
Ram
Standard 3

Trong ví dụ sau chúng tôi trả về danh sách bao gồm StudentName và StandardName tương ứng:

var studentsWithStandard = from stad in standardList
                           join s in studentList
                           on stad.StandardID equals s.StandardID
                           into sg
                               from std_grp in sg 
                               orderby stad.StandardName, std_grp.StudentName 
                               select new 
                               { 
                                    StudentName = std_grp.StudentName, 
                                    StandardName = stad.StandardName 
                               };


foreach (var group in studentsWithStandard)
{
    Console.WriteLine("{0} is in {1}", group.StudentName, group.StandardName);
}

Đây là kết quả khi biên dịch và thực thi:

John is in Standard 1
Steve is in Standard 1
Bill is in Standard 2
Ram is in Standard 2

Sắp xếp

Truy vấn sau đây trả về danh sách Student theo thứ tự tăng dần của StandardID và Age.

var sortedStudents = from s in studentList
                     orderby s.StandardID, s.Age
                     select new 
                     { 
                          StudentName = s.StudentName, 
                          Age = s.Age, 
                          StandardID = s.StandardID 
                     };

sortedStudents.ToList().ForEach(s => 
    Console.WriteLine("Student Name: {0}, Age: {1}, StandardID: {2}", 
        s.StudentName, s.Age , s.StandardID));

Đây là kết quả khi biên dịch và thực thi:

Student Name: Ron, Age: 21, StandardID: 0
Student Name: John, Age: 18, StandardID: 1
Student Name: Steve, Age: 21, StandardID: 1
Student Name: Bill, Age: 18, StandardID: 2
Student Name: Ram, Age: 20, StandardID: 2

Inner Join

var studentWithStandard = from s in studentList
                          join stad in standardList
                          on s.StandardID equals stad.StandardID 
                          select new 
                          { 
                               StudentName = s.StudentName, 
                               StandardName = stad.StandardName 
                          };

studentWithStandard.ToList().ForEach(s => 
    Console.WriteLine("{0} is in {1}", s.StudentName, s.StandardName  ));

Đây là kết quả khi biên dịch và thực thi:

John is in Standard 1
Steve is in Standard 1
Bill is in Standard 2
Ram is in Standard 2

Truy vấn lồng nhau

var nestedQueries = from s in studentList
                    where s.age > 18 && s.StandardID == 
                        (from std in standardList
                        where std.StandardName == "Standard 1"
                        select std.StandardID).FirstOrDefault()
                            select s;

nestedQueries.ToList().ForEach(s => Console.WriteLine(s.StudentName));

Đây là kết quả khi biên dịch và thực thi:

Steve


Bài viết liên quan:

Bạn sẽ tìm hiểu một số truy vấn LINQ phức tạp trong hướng dẫn này.

Từ khóa let, into trong LINQ có tác dụng gì? Hướng dẫn khai báo và sử dụng từ khóa let, into trong LINQ.

Trì hoãn thực thi truy vấn LINQ là gì? Thực thi ngay lập tức truy vấn LINQ là gì? Làm sao để thực thi truy vấn LINQ.