[LINQ] Phần 9: Các toán tử truy vấn chuẩn trong LINQ 2

440 lượt xem

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.

Bình luận Facebook

Để lại bình luận

Hãy là người đầu tiên bình luận!

Thông báo khi có
avatar
1000
wpDiscuz