Bài này tiếp tục trình bày các toán tử truy vấn chuẩn trong LINQ như ở phần 8.
1. Phân vùng dữ liệu
Phân vùng trong LINQ là toán tử chia 1 tập dữ liệu đầu vào thành các phần, mà không cần sắp xếp lại các phần tử và trả về 1 trong các phần đó. Các phương thức toán tử truy vấn chuẩn thực thi việc phân vùng được liệt kê trong bảng sau.
Tên phương thức | Mô tả | Cú pháp diễn giải truy vấn C# |
---|---|---|
Skip | Bỏ qua các phần tử theo 1 vị trí cụ thể trong tập dữ liệu | không áp dụng |
SkipWhile | Bỏ qua các phần tử dựa trên một hàm cho trước cho đến khi một phần tử không thỏa mãn điều kiện. | không áp dụng |
Take | Lấy các phần tử theo 1 vị trí cụ thể trong tập dữ liệu | không áp dụng |
TakeWhile | Lấy các phần tử dựa trên 1 hàm cho trước cho đến khi 1 phần tử không thỏa mãn điều kiện. | không áp dụng |
Ví dụ 1: Lấy các phần tử từ 1 tập các số nguyên theo 4 phương thức: Skip(), SkipWhile(), Take(), TakeWhile()
// www.dammio.com List<int> numbers = new List<int> { 1, 6, 8, 10, 14 }; // bỏ qua 1 phần tử đầu tiên var query1 = numbers.Skip(1); // bỏ qua các phần tử < 8 cho đến khi gặp phần tử >= 8 thì lấy tất cả phần tử tiếp theo var query2 = numbers.SkipWhile(x=> x < 8); // lấy 3 phần tử đầu tiên trong danh sách var query3 = numbers.Take(3); // lấy các phần tử trong khi < 7, nếu gặp phần tử >= 7 thì dừng var query4 = numbers.TakeWhile(x => x < 7); foreach (var item1 in query1) Console.WriteLine(item1); Console.WriteLine("-----------"); foreach (var item2 in query2) Console.WriteLine(item2); Console.WriteLine("-----------"); foreach (var item3 in query3) Console.WriteLine(item3); Console.WriteLine("-----------"); foreach (var item4 in query4) Console.WriteLine(item4); /* Kết quả đoạn mã trên như sau 6 8 10 14 ----------- 8 10 14 ----------- 1 6 8 ----------- 1 6 /*
2. Toán tử kết nối
Một kết nối giữa 2 nguồn dữ liệu là 1 kết nối các đối tượng trong 1 nguồn dữ liệu với các đối tượng chia sẻ cùng 1 thuộc tính chung với nguồn dữ liệu khác. Kết nối là 1 toán tử quan trọng trong các truy vấn nhắm đến các nguồn dữ liệu có các mối quan hệ với nhau không thể theo sau trực tiếp.
Các phương thức kết nối cung cấp trong framework LINQ là Join<TOuter, TInner, TKey, TResult> và GroupJoin<TOuter, TInner, TKey, TResult>. Hình sau mô tả 2 tập dữ liệu với các kiểu kết nối: left outer join, inner join, right outer join.
Tên phương thức | Mô tả | Cú pháp diễn giải truy vấn C# |
---|---|---|
Join | Kết nối 2 tập dữ liệu dựa trên các hàm chọn khóa và rút trích các cặp giá trị. | join … in … on … equals … |
GroupJoin | Kết nối 2 tập dữ liệu dựa trên các hàm chọn khóa và gom nhóm các kết quả so khớp cho mỗi phần tử. | join … in … on … equals … into … |
Ví dụ 1: Trường hợp inner join giữa lớp danh mục (Category) và lớp sản phẩm (Product). Tìm tất cả các sản phẩm sao cho trường CategoryID của lớp sản phẩm phải tồn tại trong lớp danh mục (Category).
public class Category { public int CategoryID {get; set;} public string CategoryName { get; set; } } public class Product { public int ProductID { get; set; } public int CategoryID { get; set; } // khóa ngoại public string ProductName { get; set; } } public void Test() { // www.dammio.com // 1 danh mục có nhiều sản phẩm, 1 sản phẩm chỉ duy nhất thuộc về 1 danh mục List<Category> listCategories = new List<Category>() { new Category{CategoryID=1, CategoryName="Mobile"}, new Category{CategoryID=2, CategoryName="Laptop"}, new Category{CategoryID=3, CategoryName="Camera"} }; List<Product> listProducts = new List<Product>() { new Product{ProductID=111, CategoryID=3, ProductName="Kodak 101"}, new Product{ProductID=222, CategoryID=4, ProductName="Shoes ABC"}, new Product{ProductID=333, CategoryID=2, ProductName="Dell X78"}, new Product{ProductID=444, CategoryID=1, ProductName="iPhone 7"} }; // Inner join --- dammio.com // Tìm các phần tử chung giữa 2 tập dữ liệu dựa trên việc so sánh 1 cặp khóa // Tìm các sản phẩm có CategoryID tồn tại trong danh sách danh mục (listCategories) var query = from product in listProducts join category in listCategories on product.CategoryID equals category.CategoryID select new { product.ProductID, product.ProductName, product.CategoryID, category.CategoryName }; foreach(var item in query) { Console.WriteLine("ProductID: " + item.ProductID + "--ProductName: " + item.ProductName + "--CategoryID: " + item.CategoryID + "--CategoryName: " + item.CategoryName); } /* Kết quả đoạn mã trên như sau: ProductID: 111--ProductName: Kodak 101--CategoryID: 3--CategoryName: Camera ProductID: 333--ProductName: Dell X78--CategoryID: 2--CategoryName: Laptop ProductID: 444--ProductName: iPhone 7--CategoryID: 1--CategoryName: Mobile */ }
Trong ví dụ 1, chúng ta thấy chỉ có những sản phẩm có CategoryID tồn tại trong danh sách danh mục (listCategories) mới được hiển thị. Sản phẩm “Shoes ABC” có CategoryID là 4 không tồn tại trong danh sách danh mục cho nên khi inner join sẽ không được thêm vào kết quả cuối cùng. Có 2 tập A và B kết nối với nhau theo inner join thì có thể hiểu là tìm các phần tử đều xuất hiện ở A lẫn B theo 1 điều kiện nào đó.
Ví dụ 2: Ví dụ này mô tả trường hợp left outer join. Tìm các sản phẩm có mối quan hệ với lớp danh mục thông qua trường CategoryID. Nếu trường CategoryID của sản phẩm nào không tồn tại trong danh sách danh mục thì vẫn chọn sản phẩm đó nhưng đặt giá trị trường CategoryName là “Unknown”. Các phần mã khác tương tự như ví dụ 1 ở trên.
// dammio.com // Left outer join // Kết nối lớp sản phẩm và lớp danh mục, nếu CategoryID không tồn tại ở lớp danh mục thì đặt CategoryName giá trị là "Unknown" var query1 = from product in listProducts join category in listCategories on product.CategoryID equals category.CategoryID into temp from subtemp in temp.DefaultIfEmpty() select new { product.ProductID, product.ProductName, product.CategoryID, CategoryName = subtemp == null ? "Unknown" : subtemp.CategoryName }; foreach(var item in query1) { Console.WriteLine("ProductID: " + item.ProductID + "--ProductName: " + item.ProductName + "--CategoryID: " + item.CategoryID + "--CategoryName: " + item.CategoryName); } /* Kết quả đoạn mã trên như sau: ProductID: 111--ProductName: Kodak 101--CategoryID: 3--CategoryName: Camera ProductID: 222--ProductName: Shoes ABC--CategoryID: 4--CategoryName: Unknown ProductID: 333--ProductName: Dell X78--CategoryID: 2--CategoryName: Laptop ProductID: 444--ProductName: iPhone 7--CategoryID: 1--CategoryName: Mobile */
Trong ví dụ 2, chúng ta có trường hợp left outer join. Kết quả chúng ta có tất cả 4 sản phẩm được liệt kê (tất cả sản phẩm trong danh sách sản phẩm) với sản phẩm “Shoes ABC” có CategoryID = 4 không tồn tại trong danh sách danh mục vì vậy chúng ta đặt giá trị CategoryName của nó là “Unknown” (không rõ). Trường hợp left outer join được hiểu là kết nối mọi phần tử của tập A với tập B theo điều kiện nào đó, nếu 1 phần tử tập A không thõa điều kiện thì chúng ta vẫn lấy phần tử đó, tuy nhiên giá trị liên quan của phần tử đó trên tập B có giá trị mặc định là NULL. Trường hợp right outer join tương tự như left outer join, chỉ khác là đổi bên làm mốc so sánh.
Ví dụ 3: Ví dụ này đưa ra trường hợp Group Join, tức là gom 1 nhóm dựa trên kết nối giữa 2 lớp. Chúng ta có 2 danh sách listProducts và listCategories, sau đó gom các sản phẩm theo từng danh mục vào 1 đối tượng và hiển thị kết quả.
// www.dammio.com var query2 = from category in listCategories join product in listProducts on category.CategoryID equals product.CategoryID into productgroup select new { category.CategoryID, category.CategoryName, Products = productgroup }; foreach(var item in query2) { Console.WriteLine("CategoryID: " + item.CategoryID + "---CategoryName: " + item.CategoryName); foreach (var subitem in item.Products) Console.WriteLine("------ProductID: " + subitem.ProductID + "--ProductName: " + subitem.ProductName); } /* Kết quả đoạn mã trên: CategoryID = 4 không có sản phẩm nào dựa trên kết nối cho nên sẽ không có kết quả. CategoryID: 1---CategoryName: Mobile ------ProductID: 444--ProductName: iPhone 7 CategoryID: 2---CategoryName: Laptop ------ProductID: 333--ProductName: Dell X78 CategoryID: 3---CategoryName: Camera ------ProductID: 111--ProductName: Kodak 101 /*
3. Toán tử gom nhóm
Gom nhóm là các toán tử xếp dữ liệu vào 1 nhóm để các phần tử trong mỗi nhóm chia sẻ 1 thuộc tính chung nào đó. Các phương thức toán tử truy vấn chuẩn gom dữ các phần tử dữ liệu được liệt kê trong bảng sau.
Tên phương thức | Mô tả | Cú pháp diễn giải truy vấn C# |
---|---|---|
GroupBy | Gom nhóm các phần tử chia sẻ 1 thuộc tính chung. Mỗi nhóm được thể hiện bằng 1 đối tượng IGrouping<TKey, TElement>. | group … by
-hoặc- group … by … into … |
ToLookup | Chèn các phần tử vào 1 Lookup<TKey, TElement> (từ điển một-nhiều) dựa trên 1 hàm chọn khóa. | không áp dụng |
Ví dụ 1: gom nhóm 1 danh sách các số nguyên dương thành 2 nhóm: số chẵn và số lẻ, sau đó xuất ra các phần tử của mỗi danh sách.
// www.dammio.com List<int> numbers = new List<int>() { 5, 8, 20, 18, 77, 4, 19, 29 }; // gom nhóm các số vào theo chẵn, lẻ IEnumerable<IGrouping<int, int>> query = from number in numbers group number by number % 2; foreach (var group in query) { Console.WriteLine(group.Key == 0 ? "\nCác số chẵn:" : "\nCác số lẻ:"); foreach (int i in group) Console.WriteLine(i); } /* Kết quả: Các số lẻ: 5 77 19 29 Các số chẵn: 8 20 18 4 */
Ví dụ 2: Gom các tên trong danh sách thành các nhóm với chỉ mục là các chữ cái.
// www.dammio.com List<string> listNames = new List<string>() { "An", "Anh", "Bi", "Bình", "Sơn", "Sương", "Toàn" }; // Tạo 1 Lookup để sắp xếp danh sách tên // Dùng ký tự đầu tiên của tên làm khóa (chỉ mục) // Gom các giá trị tên theo từng khóa (chỉ mục) ILookup<char, string> lookup = listNames.ToLookup(p => Convert.ToChar(p.Substring(0,1)), p => p); foreach (IGrouping<char, string> packageGroup in lookup) { // Hiển thị giá trị khóa của IGrouping. Console.WriteLine(packageGroup.Key); // Lặp qua mỗi phần tử của IGrouping và hiển thị giá trị của nó foreach (string str in packageGroup) Console.WriteLine("-----" + str); } /* Kết quả: A -----An -----Anh B -----Bi -----Bình S -----Sơn -----Sương T -----Toàn */
4. Toán tử phát sinh
Toán tử phát sinh là các toán tử dùng để tạo ra các giá trị tập dữ liệu mới với các phương thức truy vấn chuẩn như bảng sau.
Tên phương thức | Mô tả | Cú pháp diễn giải truy vấn C# |
---|---|---|
DefaultIfEmpty | Thay thế 1 tập rỗng với 1 tập có giá trị mặc định. | không áp dụng |
Empty | Trả về 1 tập rỗng | không áp dụng |
Range | Phát sinh 1 tập chứa 1 tập các số. | không áp dụng |
Repeat | Phát sinh 1 tập chứa giá trị lặp lại. | không áp dụng |
Ví dụ 1: In danh sách rỗng kiểu số int ra màn hình
// www.dammio.com List<int> numbers = new List<int>(); // In kết quả, nếu danh sách rỗng thì trả về giá trị mặc định của kiểu int là 0 foreach (int number in numbers.DefaultIfEmpty()) Console.WriteLine(number); /* Kết quả: 0 */
Ví dụ 2: Tạo 1 tập rỗng kiểu số nguyên và in kết quả số lượng phần tử của tập rỗng.
// www.dammio.com - tạo 1 tập rỗng var query = Enumerable.Empty<int>(); // In số lượng phần tử của tập rỗng có giá trị là 0 Console.WriteLine(query.Count<int>());
Ví dụ 3: Gieo 3 số liên tiếp nhau với giá trị khởi đầu là 4 bằng phương thức Range()
// www.dammio.com // Tạo danh sách 3 phần tử liên tiếp nhau có giá trị khởi đầu là 4 IEnumerable<int> numbers = Enumerable.Range(4, 3); foreach (int n in numbers) Console.WriteLine(n); /* Kết quả: 4 5 6 */
Ví dụ 4: Gieo danh sách đều là số 4 lặp lại 3 lần bằng phương thức Repeat()
// www.dammio.com // Gieo danh sách toàn bộ đều là số 4 lặp lại 3 lần IEnumerable<int> numbers = Enumerable.Repeat(4,3); foreach (int n in numbers) Console.WriteLine(n); /* Kết quả: 4 4 4 */
Như vậy bài này cung cấp nội dung về các toán tử phân vùng, kết nối, gom nhóm, phát sinh dữ liệu. Mời bạn tiếp tục theo các dõi các toán tử khác trong bài tiếp theo.
- APA:
Dammio. (2017). [LINQ] Phần 9: Các toán tử truy vấn chuẩn trong LINQ 2. https://www.dammio.com/2017/01/08/linq-phan-9-cac-toan-tu-truy-van-chuan-trong-linq-2.
- BibTeX:
@misc{dammio,
author = {Dammio},
title = {[LINQ] Phần 9: Các toán tử truy vấn chuẩn trong LINQ 2},
year = {2017},
url = {https://www.dammio.com/2017/01/08/linq-phan-9-cac-toan-tu-truy-van-chuan-trong-linq-2},
urldate = {2025-01-10}
}