CodeGym /Các khóa học /C# SELF /Kết hợp nhiều nguồn: Selec...

Kết hợp nhiều nguồn: SelectMany trong LINQ

C# SELF
Mức độ , Bài học
Có sẵn

1. Giới thiệu

Nhớ lại cách Select bình thường hoạt động nhé:

var names = users.Select(user => user.Name);

Mỗi user sẽ được map sang tên của họ — ta nhận được danh sách tên. Đơn giản đúng không! Nhưng nếu muốn lấy danh sách tất cả order của mọi user thì sao?

var ordersList = users.Select(user => user.Orders);

Ở đây sẽ ra sao? Với mỗi user ta sẽ lấy danh sách order. Kết quả là một collection các collection (IEnumerable<List<Order>>). Kiểu như kệ có nhiều hộp: muốn lấy từng order phải tự mở từng hộp (list order của user) ra.

Mình muốn "giải nén" hết đống này để làm việc như một list order bình thường. Đây là lúc SelectMany phát huy tác dụng.

So sánh: giải nén hộp

Hãy tưởng tượng tất cả đồ của bạn không để lẫn mà để trong các thùng. Mỗi thùng là một collection, ví dụ, đồ mua của một user. Nếu bạn muốn xem TẤT CẢ đồ cùng lúc (kiểu kiểm kê tổng thể), bạn sẽ không lục từng thùng rồi lại lục từng món, rồi lại tiếp tục. Dễ nhất là đổ hết đồ trong các thùng ra một đống lớn — rồi xử lý. Đó chính là cách SelectMany hoạt động.

2. SelectMany là gì?

SelectMany là một method của LINQ, nó lấy một collection các collection và biến thành một collection "phẳng". "Phẳng" ở đây nghĩa là kết quả không còn list lồng nhau nữa — mọi phần tử trong các collection con đều nằm cùng một cấp, như thể được "kéo" ra ngoài. Không còn kiểu búp bê matryoshka, chỉ còn một chuỗi giá trị dài.

Chữ ký method như sau:


IEnumerable<TResult> SelectMany<TSource, TResult>(
    this IEnumerable<TSource> source,
    Func<TSource, IEnumerable<TResult>> selector
)

Cách hoạt động rất đơn giản: bạn có một collection (ví dụ list user), mỗi phần tử lại chứa một collection khác (ví dụ list order của user đó). SelectMany lấy từng user, lấy order của họ rồi gộp lại thành một luồng order duy nhất — không còn "bọc", không còn lồng nhau. Đó là lý do nó tiện khi cần chuyển từ "list các list" sang list bình thường.

3. Ví dụ đơn giản

Ví dụ 1: List user và order của họ

Bắt đầu với model app quen thuộc nhé:

// Giả sử các class này đã có từ bài trước
class User
{
    public string Name { get; set; }
    public List<Order> Orders { get; set; }
}

class Order
{
    public int Id { get; set; }
    public string ProductName { get; set; }
}

Giả sử có list user như sau:

var users = new List<User>
{
    new User
    {
        Name = "Ivan",
        Orders = new List<Order>
        {
            new Order { Id = 1, ProductName = "Sach" },
            new Order { Id = 2, ProductName = "But" }
        }
    },
    new User
    {
        Name = "Maria",
        Orders = new List<Order>
        {
            new Order { Id = 3, ProductName = "Laptop" }
        }
    }
};

Bài toán: lấy list tất cả sản phẩm từng được mua bởi mọi user.

Select bình thường:

var ordersPerUser = users.Select(user => user.Orders);
// IEnumerable<List<Order>>

Kết quả là nhiều "hộp", mỗi hộp là list order của một user.

SelectMany:

var allOrders = users.SelectMany(user => user.Orders);
// IEnumerable<Order>

Giờ ta có một collection "dài" chứa tất cả order!

Minh họa

User Order
Ivan Sach, But
Maria Laptop
  • Sau Select: [[Sach, But], [Laptop]] — list các list
  • Sau SelectMany: [Sach, But, Laptop] — một list duy nhất

Ví dụ 2: Làm việc với nhiều cấp lồng nhau

Nếu ví dụ, mỗi order lại có list sản phẩm (giỏ hàng), thì SelectMany có thể dùng nhiều lần — kiểu như búp bê matryoshka!

class Product
{
    public string Name { get; set; }
}

class Order
{
    public int Id { get; set; }
    public List<Product> Products { get; set; }
}

class User
{
    public string Name { get; set; }
    public List<Order> Orders { get; set; }
}

var users = new List<User>
{
    new User
    {
        Name = "Petr",
        Orders = new List<Order>
        {
            new Order
            {
                Id = 10,
                Products = new List<Product>
                {
                    new Product { Name = "Dien thoai" },
                    new Product { Name = "Sac" }
                }
            }
        }
    }
};

Để lấy tất cả sản phẩm của mọi order của mọi user, dùng double SelectMany:

var allProducts = users
    .SelectMany(u => u.Orders)
    .SelectMany(o => o.Products);
// IEnumerable<Product>

Cách này bạn "giải nén" từng cấp "hộp", rồi tiếp tục cấp tiếp theo.

Ví dụ 3: Dùng SelectMany với Query Syntax

Nếu bạn thích cú pháp LINQ kiểu SQL (Query Syntax), thì SelectMany sẽ là nhiều from liên tiếp:

var allProducts =
    from user in users
    from order in user.Orders
    from product in order.Products
    select product;

Ở đây mỗi from lấy phần tử từ collection trả về bởi cái trước.


users
  └─ user1
        └─ order1 
             └─ product1, product2
        └─ order2
             └─ product3
  └─ user2
        └─ order3
             └─ product4
Sơ đồ collection lồng nhau cho SelectMany

Query Syntax sẽ duyệt hết mọi đường và trả về một list sản phẩm dài.

4. Cách dùng và lưu ý

SelectMany để chuyển đổi và lọc

Với SelectMany bạn không chỉ giải nén mà còn có thể lọc hoặc chuyển đổi phần tử cùng lúc. Ví dụ, chọn chỉ sản phẩm giá trên 1000 euro từ mọi order:

var expensiveProducts = users
    .SelectMany(u => u.Orders)
    .SelectMany(o => o.Products)
    .Where(p => p.Price > 1000);

Hoặc — nhét logic vào luôn trong SelectMany (dùng selector lồng):

var expensiveProducts = users
    .SelectMany(u => u.Orders.SelectMany(o => o.Products))
    .Where(p => p.Price > 1000);

Tùy gu thôi: cả hai đều chia nhỏ vấn đề thành các bước tuần tự.

Dùng overload SelectMany với projection

Có overload "xịn" hơn cho phép vừa giải nén vừa tạo kết quả luôn. Ví dụ: lấy cặp "Tên user — Tên sản phẩm":

var userProductPairs = users.SelectMany(
    user => user.Orders.SelectMany(order => order.Products),
    (user, product) => new { user.Name, product.Name }
);

Cách này hoạt động sao? Tham số đầu — user => user.Orders.SelectMany(order => order.Products) — giải nén hết sản phẩm của user, tham số hai — kết hợp object user gốc và product trong enumerator lồng.

5. Lưu ý và lỗi thường gặp

Bạn có thể gặp trường hợp quên dùng SelectMany và thay vì chuỗi phẳng lại ra mảng "hộp". Ví dụ, dùng Select thường thay vì SelectMany khi cần giải nén hết phần tử. Kết quả là không thể duyệt hay xử lý phần tử trực tiếp.

Rất dễ rối nếu collection lồng nhau, nhất là khi lồng nhiều cấp. Hãy nhớ: nếu cần kết quả là list phẳng chứ không phải list các list, cứ mạnh dạn dùng SelectMany.

Dùng thực tế ở đâu?

Bất cứ bài toán nào bạn có object-collection, mỗi phần tử lại có collection riêng (ví dụ "user — order", "khoa hoc — sinh vien", "tin tuc — binh luan"), bạn sẽ quay lại với SelectMany. Khi phỏng vấn đây là chủ đề "ưa thích": không phải ai mới học cũng phân biệt được SelectSelectMany và biết giải nén collection đúng cách.

Trong project thực tế, nó giúp code gọn, đẹp, nhanh mà không bị lồng nhiều cấp. Trên nền tảng .NET thì đây là cách chuẩn và tối ưu để xử lý collection "nhiều lớp".

Khác biệt giữa SelectSelectMany

Method Select giữ nguyên cấu trúc: nếu dùng với collection user, mỗi user có list order, kết quả là collection các collection — ví dụ [[A, B], [C]]. Dùng khi bạn muốn xử lý theo nhóm (ví dụ theo user).

Còn SelectMany thì "dẹp phẳng" — gộp hết collection con thành một chuỗi chung: [A, B, C]. Dùng khi không cần nhóm, chỉ muốn xử lý mọi phần tử bên trong như một khối — ví dụ tìm tất cả sản phẩm, bất kể thuộc user hay order nào.


+---------------------+         +--------------------+
| users               |         | allOrders          |
+---------------------+         +--------------------+
| Ivan: [A, B]        | --+   +-> Order A           |
| Maria: [C]          | --+ | +-> Order B           |
+---------------------+   | | +-> Order C           |
                          | | +--------------------+
     Select:              | |
   [[A, B], [C]] <--------+ +
                          |
     SelectMany: <--------+
   [A, B, C]
Sơ đồ khác biệt giữa SelectSelectMany

Nói nôm na:

  • Select → "cho mình list order theo user";
  • SelectMany → "cho mình tất cả order luôn".
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION