- Giới thiệu LINQ
- Câu truy vấn LINQ
- Mệnh đề from
- Mệnh đề select
- Lọc dữ liệu với where
- Lọc dữ liệu - from kết hợp
- Sắp xếp kết quả với orderby
- Mệnh đề group ... by
- Sử dụng biến trong truy vấn
- Mệnh đề join
Giới thiệu LINQ, LINQ là gì?
LINQ (Language Integrated Query) - ngôn ngữ truy vấn tích hợp - nó tích hợp cú pháp truy vấn (gần giống các câu lệnh SQL) vào bên trong ngôn ngữ lập trình C#, cho nó C# khả năng truy cập các nguồn dữ liệu khác nhau (SQL Db, XML, List ...) với cùng cú pháp.
Phần này trình bày LINQ trên dữ liệu đơn giản, mục đích để hiểu khả năng của LINQ trước khi sử dụng nó ở các chuyên đề chuyên sâu trong truy vấn cơ sở dữ liệu, truy vấn dữ liệu XML ...
LINQ hoạt động trên những kiểu tập hợp có khả năng duyệt qua nó (xem thêm Collection, List trong C#). Để sử dụng LINQ thì nạp hai thư viện Generic và Linq:
using System.Collections.Generic; using System.Linq;
Video bài học về LINQ
Nguồn dữ liệu phục vụ cho LINQ, phải là các đối tượng lớp triển khai giao diện (interface) IEnumerable và IEnumerable<T> tức là các mảng, danh sách thuộc Collection đã biết ở phần trước.
Để thực hành những vấn đề về LINQ trong bài viết này thông qua các ví dụ, ta tạo ra một nguồn dữ liệu đó là một danh sách (sẽ dùng lớp List) các sản phẩm (các sản phẩm sẽ sử dụng lớp Product tự xây dựng). Xây dựng lớp Product như sau:
public class Product { public int ID {set; get;} public string Name {set; get;} // tên public double Price {set; get;} // giá public string[] Colors {set; get;} // các màu sắc public int Brand {set; get;} // ID Nhãn hiệu, hãng public Product(int id, string name, double price, string[] colors, int brand) { ID = id; Name = name; Price = price; Colors = colors; Brand = brand; } // Lấy chuỗi thông tin sản phẳm gồm ID, Name, Price override public string ToString() => $"{ID,3} {Name, 12} {Price, 5} {Brand, 2} {string.Join(",", Colors)}"; }
Xây dựng lớp Brand để biểu diễn nhãn hiệu hàng hóa:
public class Brand { public string Name {set; get;} public int ID {set; get;} }
Khởi tạo ra 2 đối tượng danh sách dùng để thực hiện các truy vấn linq: danh sách sản phẩm products, danh sách nhãn hiệu brands
var brands = new List<Brand>() { new Brand{ID = 1, Name = "Công ty AAA"}, new Brand{ID = 2, Name = "Công ty BBB"}, new Brand{ID = 4, Name = "Công ty CCC"}, }; var products = new List<Product>() { new Product(1, "Bàn trà", 400, new string[] {"Xám", "Xanh"}, 2), new Product(2, "Tranh treo", 400, new string[] {"Vàng", "Xanh"}, 1), new Product(3, "Đèn trùm", 500, new string[] {"Trắng"}, 3), new Product(4, "Bàn học", 200, new string[] {"Trắng", "Xanh"}, 1), new Product(5, "Túi da", 300, new string[] {"Đỏ", "Đen", "Vàng"}, 2), new Product(6, "Giường ngủ", 500, new string[] {"Trắng"}, 2), new Product(7, "Tủ áo", 600, new string[] {"Trắng"}, 3), };
Như vậy bạn đã có danh sách các sản phẩm mẫu và nhãn hiệu, giờ là lúc dùng cú pháp LINQ để truy vấn đến tập dữ liệu này.
Viết câu truy vấn LINQ đầu tiên
Hãy thử câu lệnh LINQ: lọc ra những sản phẩm có giá bằng 400.
public class Products { // ... // In ra các sản phẩm có giá 400 public static void ProductPrice400() { // Viết câu quy vấn, lưu vào ketqua var ketqua = from product in products where product.Price == 400 select product; foreach (var product in ketqua) Console.WriteLine(product.ToString()); } // ... }
Trong hàm Main chạy thử:
Products.ProductPrice400(); // Kết quả // ID 3 - Bàn trà, giá 400 // ID 4 - Tranh treo, giá 400
Trong câu truy vấn bạn thấy xuất hiện các từ from, where, select đó là những từ khóa của C# để viết mệnh đề truy vấn LINQ, chúng khá giống SQL.
from
và kết thúc bằng mệnh đề select
hoặc group
, giữa chúng là những mệnh đề where
, orderby
, join
, let
Mệnh đề from ...in ...
Mệnh đề from để xác định nguồn dữ liệu mà truy vấn sẽ thực hiện, nguồn dữ liệu là tập hợp những phần tử lưu trong đối tượng có kiểu lớp triển khai giao diện IEnumerable như mảng Array, List ...
Ví dụ, products ở trên là kiểu List (nó triển khai IEnumerable),
thì một giải (một đoạn, hoặc tất cả) các phần tử trong nó
(ví dụ product in products
)
có thể làm nguồn, vậy mở đầu truy vấn bằng mệnh đề from
sẽ là:
from product in produts
- products là nguồn dữ liệu
- product là tên bạn tùy đặt đại diện cho phần tử trong nguồn, tên này sẽ được dùng bởi các mệnh đề theo sau.
Mệnh đề select
Một câu truy vấn phải kết thúc bằng mệnh đề select hoặc group. Mệnh đề select chỉ ra các dữ liệu được lấy ra (xuất ra) của câu lệnh LINQ.
Ví dụ:
-
var ketqua = from product in products select product;
Có nghĩa là với mỗi product trong products, dữ liệu lấy ra là các product đó (trả về collection gồm các phần tử Product).
-
Bạn có thể chỉ lấy ra một loại dữ liệu nào đó của phần tử , ví dụ:
var ketqua = from product in products select product.Name; foreach (var name in ketqua) Console.WriteLine(name); // Bàn trà // Túi da //...
Câu LINQ trên có nghĩa là với mỗi product có trong products lấy ra tên (Name) của nó (trả về một collection gồm các phần tử chuỗi)
-
Bạn có thể trả về những đối tượng phức tạp mà dữ liệu được trích xuất ra
from ... in ...
hay những dữ liệu do tính toán ... Ví dụ trả về kiểu vô danh chứa các trường thông tin (xem thêm kiểu vô danh C# ) trả về với toán tửnew {}
var ketqua = from product in products select new { ten = product.Name.ToUpper(), mausac = string.Join(',', product.Colors) }; foreach (var item in ketqua) Console.WriteLine(item.ten + " - " + item.mausac); // Bàn trà - Xám,Xanh // Tranh treo - Vàng,Xanh
Có nghĩa là với mỗi sản phẩm lấy ra, trả về một đối tượng chứa ten lấy bằng tên sản phẩm chuyển hết thành in hoa, mausac là chuỗi nối các màu sắc của sản phẩm.
Mệnh đề where lọc dữ liệu trong LINQ
Mệnh đề where để lọc dữ liệu, sau từ khóa where là biểu thức logic xác định các phần tử lọc ra. Ví dụ:
where product.Price == 500
Bạn có thể viết các điều kiện phức tạp
where (product.Price >= 600 && product.Price < 700) || product.Name.StartsWith("Bàn")
Thậm chí, trong một truy vấn có thể viết nhiều mệnh đề where
var ketqua = from product in products where product.Price >= 500 where product.Name.StartsWith("Giường") select product; // ID 6 - Giường ngủ, giá 500
from kết hợp
Để lọc dữ liệu phức tạp hơn, có thể dùng From kết hợp để lọc phức tạp và chi tiết hơn. Ví dụ, mỗi tên sản phẩm nó có một mảng màu. Lọc ra những sản phẩm có chứa màu nào đó.
var ketqua = from product in products from color in product.Colors where product.Price < 500 where color == "Vàng" select product; foreach (var product in ketqua) Console.WriteLine(product.ToString()); // ID 2 - Túi da, giá 300 // ID 4 - Tranh treo, giá 400
Mệnh đề orderby sắp xếp kết quả trong LINQ
Để sắp xếp kết quả sử dụng orderby
viết sau mệnh đề where nếu có,
ví dụ sắp xếp tăng dần (từ nhỏ đến lớn) theo thuộc tính
dữ liệu (hoặc thuộc tính) thuoctinh
thì viết
orderby thuoctinh
Nếu muốn xếp theo thứ tự giảm dần (lớn đến bé)
orderby thuoctinh descending
Ví dụ:
var ketqua = from product in products where product.Price <= 300 orderby product.Price descending select product; foreach (var product in ketqua) Console.WriteLine($"{product.Name} - {product.Price}"); // Túi da - 300 // Bàn học - 200
Cũng có thể sắp xếp theo nhiều dữ liệu, viết cách nhau bởi ,
orderby thuoctinh1 descending, thuoctinh2, thuoctinh3 descending ...
Mệnh đề group ... by
Cuối truy vấn có thể sử dụng group thay cho select, nếu sử dụng group thì nó trả về theo từng nhóm
(nhóm lại theo trường dữ liệu nào đó),
mỗi phần tử của cấu truy vấn trả về là kiểu IGrouping<TKey,TElement>
,
chứa các phần tử thuộc một nhóm.
Ví dụ, lấy sản phẩm có giá từ 400 - 500, nhóm lại theo giá (cùng giá cho vào một nhóm)
var ketqua = from product in products where product.Price >=400 && product.Price <= 500 group product by product.Price; foreach (var group in ketqua) { // Key là giá trị dùng để phân loại (nhóm): là giá Console.WriteLine(group.Key); foreach (var product in group) { Console.WriteLine($" {product.Name} - {product.Price}"); } } // 400 // Bàn trà - 400 // Tranh treo - 400 // 500 // Đèn trùm - 500 // Giường ngủ - 500
Bạn có thể lưu tạm group trong truy vấn vào một biến bằng cách sử dụng into, sau đó thi hành các mệnh đề khác trên biến tạm và dùng mệnh đề select để trả về kết quả
// Kết quả giống câu truy vấn trên, nhưng các nhóm xếp từ lớn xuống nhỏ var ketqua = from product in products where product.Price >=400 && product.Price <= 500 group product by product.Price into gr orderby gr.Key descending select gr; // 500 // Đèn trùm - 500 // Giường ngủ - 500 // 400 // Bàn trà - 400 // Tranh treo - 400
let - dùng biến trong truy vấn
Dùng thêm biến vào LINQ, lưu kết quả của một biểu thức tính toán nào đó,
thêm vào mệnh đề bằng cách viết let tenvien = biểu_thức
, ví dụ với mỗi loại giá - có bao nhiêu
sản phẩm
var ketqua = from product in products // các sản phẩm trong products group product by product.Price into gr // nhóm thành gr theo giá let count = gr.Count() // số phần tử trong nhóm select new { // trả về giá và số sản phầm có giá này price = gr.Key, number_product = count }; foreach (var item in ketqua) { Console.WriteLine($"{item.price} - {item.number_product}"); } // 200 - 1 // 300 - 1 // 400 - 2 // 500 - 2 // 600 - 1
Mệnh đề join - khớp nối nguồn truy vấn LINQ
join là thực hiện kết hợp hai nguyền dữ liệu lại với nhau để truy vấn thông tin, ví dụ trong mỗi
sản phẩm đều có Brand
nó là ID của nhãn - vậy mỗi sản phẩm dùng thông tin này để có được thông tin chi tiết
về nhãn hàng mà nhãn hàng lại lưu ở nguồn khác.
Các sản phẩm ở ví dụ phía trên, sản phẩm nào có Brand là 2 thì tra cứu ở Brand.brands thì tên nhãn là "Công ty AAA"
Giờ muốn thực hành truy vấn kết hợp khớp nối giữa hai nguồn dữ liệu danh sách sản phẩm products và danh sách nhãn hàng brands
Inner join
Để kết nối, dùng mệnh đề join
để chỉ ra nguồn (nguồn bên phải join)
sẽ kết nối với nguồn của from
(nguồn bên trái join), tiếp theo chỉ ra sự dàng buộc các phần tử
bằng từ khóa on
Ví dụ, truy vấn sản phẩm, mỗi sản phẩm căn cứ vào Brand ID của nó - lấy tên Brand tương ứng
var ketqua = from product in products join brand in brands on product.Brand equals brand.ID select new { name = product.Name, brand = brand.Name, price = product.Price }; foreach (var item in ketqua) { Console.WriteLine($"{item.name,10} {item.price, 4} {item.brand,12}"); } // Túi da 300 Công ty BBB // Bàn trà 400 Công ty BBB // Tranh treo 400 Công ty AAA // Giường ngủ 500 Công ty BBB
Phân tích truy vấn trên ta thấy
- nguồn bên trái join là :
product in products
- nguồn bên phải join là :
brand in brands
- liên kệ kết nối là:
on product.Brand equals brand.ID
(Brand trong product bằng (equals
) ID của brand)
Với truy vấn join trên, chỉ những dữ liệu product mà có brand tương ứng mới trả về, nên nó gọi là inner join (tức giá trị product.brand hay brand.ID có ở cả 2 nguồn). Kết quả trả về có 4 dòng như trên. Để ý trong danh sách sản phẩm có sản phẩm "Tủ áo", sản phẩm này có brand là 3, nhưng ở danh sách brands lại không có phần tử nào ID bằng 3 nên truy vấn trên sẽ bỏ sản phẩm "Tủ áo"
Kỹ thuật inner join trên giống với inner join trong SQL, bạn có thể xem hình vẽ mô tả trực quan tại Khớp nối bảng SQL
Left join
Trong truy vấn trên, sản phẩm nào không tìm được thông tin brand ở ngồn bên phải join thì sẽ bỏ qua. Giờ muốn, các sản phẩm kể cả không thấy brand cũng trả về - có nghĩa nguồn bên trái lấy không phụ thuộc vào bên phải thì dùng kỹ thuật left join như sau:
var ketqua = from product in products join brand in brands on product.Brand equals brand.ID into t from brand in t.DefaultIfEmpty() select new { name = product.Name, brand = (brand == null) ? "NO-BRAND" : brand.Name, price = product.Price }; foreach (var item in ketqua) { Console.WriteLine($"{item.name,10} {item.price, 4} {item.brand,12}"); } // Bàn học 200 Công ty AAA // Túi da 300 Công ty BBB // Bàn trà 400 Công ty BBB // Tranh treo 400 Công ty AAA // Đèn trùm 500 NO-BRAND // Giường ngủ 500 Công ty BBB // Tủ áo 600 NO-BRAND
Khi truy vấn, các brand tương ứng với product gồm cả nhưng brand bằng null tương ứng với sản phẩm không thấy brand được lưu vào nguồn tạm đặt tên là t. Sau đó truy vấn lấy brand trên DefaultIfEmpty() nguồn tạm này, đảm bảo các sản phẩm kể cả không thấy brand (brand bằng null) cũng trả về.
Khi lấy tên brand, nếu null thì thay bằng chữ "NO-BRAND" brand = (brand == null) ? "NO-BRAND" : brand.Name
Mã nguồn ví dụ tại CS018_LINQ (git) hoặc tải về ex018