1. Giới thiệu
Hãy tưởng tượng bạn đến thư viện và cần tìm tất cả sách về lập trình xuất bản sau năm 2020, được sắp xếp theo tên tác giả. Chắc chắn bạn sẽ không chạy từ kệ này sang kệ khác và kiểm tra từng cuốn sách một cách thủ công đúng không? Bạn sẽ nhờ thủ thư, người biết cách tìm nhanh thứ bạn cần.
LINQ (Language Integrated Query, hay "Truy vấn tích hợp ngôn ngữ") – chính là "thủ thư thông minh" của chúng ta, "SQL-động cơ" (tức là công cụ để viết truy vấn dữ liệu) ngay trong C#!
Hãy nghĩ về LINQ như một ngôn ngữ cho phép bạn mô tả bạn muốn lấy gì từ dữ liệu, chứ không phải làm thế nào để lấy nó. Thay vì phải yêu cầu: "Lấy phần tử đầu tiên, kiểm tra điều kiện, nếu đúng thì thêm vào list mới, sau đó chuyển sang phần tử thứ hai...", bạn chỉ cần nói: "Đưa tôi tất cả sản phẩm có giá lớn hơn 1000". Và C# sẽ tự biết cách thực hiện hiệu quả nhất.
Ý tưởng chính của LINQ: cung cấp cú pháp chuẩn cho truy vấn bất kỳ nguồn dữ liệu nào mà implement interface IEnumerable<T>. Và điều này thật tuyệt, vì List<T>, array, HashSet<T> – tất cả đều implement IEnumerable<T>. Nếu dữ liệu của bạn nằm trong database, thì các thư viện đặc biệt (ví dụ Entity Framework) sẽ chuyển LINQ-query của bạn thành SQL thực sự!
LINQ xuất hiện trong C# hơn 10 năm trước. Đó là một sự kiện quan trọng đã thay đổi cách làm việc với dữ liệu trong .NET. Trước LINQ, dev phải viết rất nhiều code mẫu để lọc, sắp xếp và chuyển đổi collection. Hoặc dùng string SQL-query, mà compiler không kiểm tra và dễ bị lỗi runtime. LINQ mang khái niệm "truy vấn" vào tận ngôn ngữ lập trình, làm cho chúng type-safe và dễ đọc hơn nhiều.
Nhiều người cho rằng LINQ là một trong những cải tiến lớn nhất của C# kể từ khi có Generics.
2. Lợi ích của LINQ: Tại sao ai cũng thích?
Ngắn gọn và dễ đọc: Để lọc, ví dụ, sản phẩm có giá lớn hơn 1000 trong một database lớn, trước đây phải viết vài dòng code. Với LINQ, chỉ cần một dòng. Code ít hơn — ít bug hơn, dễ đọc và dễ hiểu hơn. Code gần với ngôn ngữ tự nhiên hơn.
Joke lập trình viên: "Càng viết ít code, mình càng nhét được ít bug vào đó." LINQ giúp bạn điều này!
Mạnh mẽ và linh hoạt: LINQ cung cấp rất nhiều operation: lọc (Where), chuyển đổi (Select), sắp xếp (OrderBy), nhóm (GroupBy), tổng hợp (Sum, Average), join (Join) và nhiều nữa. Bạn có thể giải quyết các bài toán xử lý dữ liệu phức tạp bằng cách kết hợp các operation này.
Type-safe: Cái này quan trọng lắm! Khi bạn viết SQL-query dưới dạng string, compiler không biết gì về nó. Nếu bạn sai tên cột, chỉ khi chạy chương trình mới biết lỗi. Với LINQ, compiler C# kiểm tra query của bạn ngay lúc compile. Nếu bạn truy vấn field không tồn tại trong object Product, compiler sẽ báo lỗi ngay. Kiểu như có một "checker" cá nhân bắt lỗi chính tả của bạn trước khi người khác thấy.
Tích hợp với ngôn ngữ: LINQ-query không phải là string "ma thuật" nào đó. Nó là construct C# thực thụ, dùng lambda expression quen thuộc và làm việc với các kiểu dữ liệu bạn đã biết. Việc chuyển sang LINQ rất mượt mà.
Deferred Execution (Thực thi trì hoãn): Đây là một trong những concept ngầu nhất và cũng khó nhất của LINQ, nhưng rất quan trọng. Ý là LINQ-query không chạy ngay khi bạn viết nó. Nó "được xây dựng" và chờ đến khi bạn thực sự cần kết quả (ví dụ, bắt đầu duyệt nó bằng foreach). Điều này giúp tối ưu hóa query, nhất là khi làm việc với dữ liệu lớn. Chúng ta sẽ nói kỹ hơn ở Bài giảng 166. Giờ chỉ cần nhớ: LINQ thông minh, nó không làm việc thừa.
Đa năng và mở rộng: LINQ không chỉ cho list trong memory. Có nhiều "provider" LINQ khác nhau:
- LINQ to Objects: cho collection trong memory (List<T>, array, v.v.).
- LINQ to SQL / Entity Framework Core: cho SQL database (LINQ-query của bạn được chuyển thành SQL-query).
- LINQ to XML: để làm việc với XML-document.
- Và còn nhiều nữa.
Nghĩa là, khi đã quen LINQ, bạn có thể làm việc với dữ liệu từ nhiều nguồn khác nhau, dùng cùng một logic truy vấn.
3. Vấn đề khi làm việc với collection "kiểu cũ"
Trước khi có LINQ, code xử lý collection trong C# thường như này:
var products = new List<Product> { /* ... */ };
var expensive = new List<Product>();
foreach (var prod in products)
{
if (prod.Price > 100)
expensive.Add(prod);
}
expensive.Sort((a, b) => a.Price.CompareTo(b.Price));
foreach (var item in expensive)
Console.WriteLine(item.Name);
Kiểu này rất phổ biến: đầu tiên lọc thủ công, rồi sort — phải tạo list trung gian. Logic càng nhiều, code càng dài, càng dễ lỗi và nhiều biến phụ. Tất cả giống như xưởng lắp ráp đồ gỗ: có đủ chi tiết, nhưng lắp ráp thì thủ công và mệt mỏi.
Alice ở xứ sở vòng lặp
Hãy tưởng tượng bạn có một list user rất lớn, và bạn muốn lấy tên những người trên 18 tuổi và sống ở thành phố Neonvil, sắp xếp theo alphabet và lấy ba người đầu tiên. Bạn sẽ phải viết vòng lặp lồng nhau, điều kiện, sort, list phụ... hoặc chỉ cần dùng LINQ.
4. LINQ-query trông như thế nào? Ví dụ đơn giản
LINQ có hai cú pháp chính:
- Method Syntax (chuỗi method)
- Query Syntax (ngôn ngữ giống SQL)
Thường thì dùng Method Syntax nhiều hơn, nhất là trong project hiện đại. Đây là ví dụ dựa trên app của chúng ta:
var expensive = products
.Where(p => p.Price > 100)
.OrderBy(p => p.Price)
.Select(p => p.Name)
.Take(3);
foreach (var name in expensive)
Console.WriteLine(name);
Rất rõ ràng: lọc, sort, chọn field cụ thể, giới hạn số lượng. Code tối thiểu — ý nghĩa tối đa.
Cùng ví dụ với Query Syntax
var expensive = from p in products
where p.Price > 100
orderby p.Price
select p.Name;
foreach (var name in expensive.Take(3))
Console.WriteLine(name);
Cả hai cách đều cho kết quả như nhau — chọn cách bạn thích (nhưng method-syntax vẫn phổ biến hơn).
5. Kiến trúc LINQ: Bên trong có gì?
LINQ làm việc với bất kỳ collection nào implement interface IEnumerable<T> (hoặc IQueryable<T>, sẽ nói sau).
Thành phần chính của LINQ
| Thành phần | Mô tả ngắn |
|---|---|
| Class mở rộng LINQ | Static method (, , v.v.) trong class |
| Delegate | Thường dùng nhất là và |
| Deferred execution | Query chỉ được tính toán khi duyệt lần đầu (ví dụ qua ) |
| Query Provider | (cho LINQ to SQL, LINQ to Entities) — chuyển chuỗi method thành SQL-query v.v. |
Sơ đồ trực quan
Collection (List<Product>, T[], ...)
│
▼
LINQ-method (Where, OrderBy, Select...)
│
▼
Query (IEnumerable<T>)
│
▼
Thực thi thực tế (foreach, ToList, Count, ...)
6. Ví dụ: Phân tích từng bước LINQ-chain trong app của chúng ta
Quay lại mini-app với class Product:
// Class sản phẩm
class Product
{
public string Name { get; set; }
public double Price { get; set; }
}
Đây là danh sách sản phẩm:
var products = new List<Product>
{
new Product { Name = "Phô mai", Price = 250.5 },
new Product { Name = "Bánh mì", Price = 30 },
new Product { Name = "Sữa", Price = 80 },
new Product { Name = "Cà phê", Price = 330 },
new Product { Name = "Bơ", Price = 140 }
};
Giả sử nhiệm vụ là: in ra màn hình tên tất cả sản phẩm đắt hơn 100 euro, sắp xếp theo giá.
Cách cũ
var filtered = new List<Product>();
foreach (var p in products)
{
if (p.Price > 100)
filtered.Add(p);
}
filtered.Sort((a, b) => a.Price.CompareTo(b.Price));
foreach (var p in filtered)
Console.WriteLine(p.Name);
Cách LINQ
var expensive = products
.Where(p => p.Price > 100)
.OrderBy(p => p.Price)
.Select(p => p.Name);
foreach (var name in expensive)
Console.WriteLine(name);
Ngay dòng đầu tiên, ta mô tả đúng bản chất bài toán: "Lọc những ai có Price > 100, sắp xếp theo giá, lấy tên".
7. Các thao tác LINQ thường gặp và cách làm "kiểu cũ"
| Thao tác | Logic "kiểu cũ" | LINQ |
|---|---|---|
| Lọc | foreach + if + Add | |
| Chuyển đổi (chọn field) | foreach + Add(field) | |
| Sắp xếp | Sort(comparer) | , |
| Giá trị duy nhất | foreach + contains + Add | |
| Đếm | foreach + counter++ | , |
| Kiểm tra điều kiện | foreach + if | , |
| Tìm phần tử đầu/cuối | foreach + if + break | , , |
| Giới hạn số lượng | foreach + counter + break | , |
8. Thực hành: Thêm LINQ vào app
Lấy code cơ bản của app (Product-list) và thử làm vài thao tác hữu ích với LINQ.
Lọc và sắp xếp
// Chọn sản phẩm rẻ hơn 200 euro và sắp xếp theo tên
var cheapProducts = products
.Where(p => p.Price < 200)
.OrderBy(p => p.Name);
foreach (var p in cheapProducts)
Console.WriteLine($"{p.Name}: {p.Price} euro");
Chuyển đổi (projection) collection
// Lấy list giá (double)
var prices = products.Select(p => p.Price);
foreach (var price in prices)
Console.WriteLine(price);
Tìm phần tử đầu tiên phù hợp
// Sản phẩm đầu tiên có tên bắt đầu bằng "C"
var firstK = products.FirstOrDefault(p => p.Name.StartsWith("C"));
Console.WriteLine(firstK?.Name ?? "Không tìm thấy");
Đếm số sản phẩm đắt hơn 100 euro
int count = products.Count(p => p.Price > 100);
Console.WriteLine($"Có {count} sản phẩm như vậy.");
Từ bài này trở đi, chúng ta sẽ dùng LINQ thường xuyên để xử lý collection, lọc, chọn dữ liệu cần thiết, tổng hợp và nhiều thứ khác. Các method mới và tính năng LINQ (cả những thứ mới của .NET 9!) sẽ được phân tích ở các bài tiếp theo, còn bây giờ — cứ mạnh dạn thử nghiệm với các query đơn giản để cảm nhận sức mạnh của công cụ này nhé!
GO TO FULL VERSION