+4

Khi nào sử dung LinQ <query> tốt hơn dùng LinQ <method>

Mayfest2023

Thật lòng mà nói, LinQ vô cùng bá đạo, mạnh mẽ linh hoạt và đẹp đẽ. Mình yêu nó ngay từ cái nhìn đầu tiên, mình nghĩ các bạn cũng sẽ như vậy thôi 😄. Nếu tưởng tượng LinQ với một thứ gì đó ngoài đời thường, thì mình nghĩ ngay tới một chiếc smartphone - không ai phủ nhận tầm quan trọng của nó đúng không nào - mặc định là vậy đi.

LinQ có hai cách viết, một là viết kiểu query (kiểu này giống giống sql, team mình dùng cái tên LinQ sql :v), hai là viết kiểu method hoặc gọi là lambda. Vậy bây giờ chúng ta có câu hỏi: nên sử dụng linq method hay linq query? ai tốt hơn, cũng như cách sử dụng linq query với method như thế nào.

Ví dụ:

var numbers = Enumerable.Range(1, 100); // 1, 2, ..., 100

// query syntax:
var query = from n in numbers
    where n % 2 == 0
    select n * 2;

// method syntax:
var method = numbers
    .Where(n => n % 2 == 0)
    .Select(n => n * 2);

Vậy khi nào thì LinQ query tốt hơn LinQ method nhỉ? Mình nên xài cái nào đây?

Mình không biết các developer khác như thế nào, chứ riêng cá nhân mình, mình có cảm tình với LinQ method hơn, biết nói sao nhỉ, kiểu như thích thì thích thôi vậy đó. Có thể một phần khi tiếp xúc với JS, dùng callback nhiều nên dễ hiểu LinQ method hơn chăng.

Thật ra không có cái nào mạnh hơn cái nào đâu. Các bạn có thể dùng tool để chuyển từ LinQ query sang LinQ method đấy và ngược lại. Nên nếu bạn mong muốn cái nào có performance tốt hơn cái nào thì chúng như nhau cả thôi. Tuy nhiên, tùy vào trường hợp mà sử dụng, LinQ method trong vài trường hợp sẽ bộc lộ điểm yếu và đây là lúc LinQ query được dịp thể hiện. Cho nên ý mình ở đây tốt hơn nghĩa là đọc dễ hiểu hơn, dễ bảo trì hơn.

Nên bây giờ mình sẽ giải thích vì sao mình nói thế và show ra các điểm mạnh của LinQ query, sau cùng đi đến kết luận để phần nào các bạn tham khảo nhé.

Sử dụng từ khóa let

LinQ query có một thứ ghi điểm ở phần so sánh này, đó chính là sử dụng được từ khóa let. Nó cho phép bạn tạm thời chứa kết quả vào một biến và sử dụng nó ở nhiều chỗ khác nhau.

var querySyntax =
    from person in persons
    let yearsWorking = GetYearsWorking(person)
    where yearsWorking > 4
    orderby yearsWorking
    select person.Name;
 
var methodSyntax = persons
    .Select(person => new { 
        YearsWorking = GetYearsWorking(person),
        Name = person.Name
    })
    .Where(x => x.YearsWorking > 4)
    .OrderBy(x => x.YearsWorking)
    .Select(x => x.Name);

Như bạn thấy dùng let làm cho code của linq query nhìn dễ đọc hơn. Đối với linq method thì để làm được điều này ta phải tạo một anonymous class để chứa kết quả. Nhìn chung khi nào bạn cần một code dễ đọc, gọi một query con hay một hàm nhiều lần, cần lưu lại kết quả để sử dụng thì xài linq query với từ khóa let.

Query từ nhiều "nguồn" trong linq với from

Nếu bạn cần lấy data từ nhiều nguồn khác nhau thì làm thế nào, nguồn ở đây hiểu là dữ liệu từ một table, object, array hay collection. Hãy nhìn qua cách sử dụng trong linq query.

var rows = Enumerable.Range(1, 3); // 1, 2, 3
var columns = new string[] { "A", "B", "C"};
 
var querySyntax = from row in rows
               from col in columns
               select $"cell [{row}, {col}]";
 
var methodSyntax = 
    rows.SelectMany(row => columns, (r, c) => $"cell [{r}, {c}]");
 
foreach (var cell in methodSyntax)
{
    Console.WriteLine(cell);
}
// output:
// cell[1, A]
// cell[1, B]
// cell[1, C]
// cell[2, A]
// cell[2, B]
// cell[2, C]
// cell[3, A]
// cell[3, B]
// cell[3, C]

Mục đích với đoạn code trên là lấy data đồng thời từ hay nguồn dữ liệu là rowscolumns. Với từ khóa from thì bạn thấy đấy, code linq query nhìn rất dễ hiểu so với linq method phải không nào. Mình dám chắc linq method bạn hơi tốn chút nơ-ron thần kinh để đọc hiểu đấy 😛.

OrderBy hay orderby khi cần order data theo nhiều điều kiện

Với hai cú pháp đều có thể order dữ liệu theo nhiều điều kiện với nhau được. Bây giờ cũng xem qua ví dụ sau. Mình cần sắp xếp nhân viên trong công ty theo tuổi (age) sau đó là theo thu nhập(income). Có nghĩa là sắp xếp theo age xong xuôi rồi sẽ sắp xếp theo income.

var people = new Person[]
{
    new Person() { Age = 20, Income = 5000, Name = "Viktor" },
    new Person() { Age = 30, Income = 8000, Name = "Merry" },
    new Person() { Age = 30, Income = 7000, Name = "Happy" },
    new Person() { Age = 20, Income = 4000, Name = "Tony Stark" },
    new Person() { Age = 20, Income = 6000, Name = "Madarin" },
    new Person() { Age = 30, Income = 5500, Name = "Pepper" },
};
 
var querySyntax = from person in people
    orderby person.Age, person.Income
    select $"{person.Age} {person.Income} {person.Name}";

var methodSyntax = people
    .OrderBy(person => person.Age)
    .ThenBy(person => person.Income)
    .Select(person => $"{person.Age} {person.Income} {person.Name}");
 
// result
// 20 4000 Tony Stark
// 20 5000 Viktor
// 20 6000 Madarin
// 30 5500 Pepper
// 30 7000 Happy
// 30 8000 Merry

Chỗ này thì tùy vào cá nhân mỗi người thấy thích cách viết nào hơn thôi. Cả hai cú pháp đều làm rất tốt chỗ này. Tuy nhiên với linq query, orderby trên cùng một line code nhìn có vẻ gọn gàng xúc tích hơn.

Tiếp theo sẽ đến phần mà mình nghĩ tạo nên khác biệt khá lớn giữa hai cu cậu này, và chính điểm này làm mình thích linq query hơn.

Sử dụng GroupBy hay group hay hơn?

Cả hay đều giống nhau về mặt tính năng, đều dùng để group dữ liệu. group thì dùng trong linq query còn GroupBy là một extention method trong linq method. Vậy chính cách viết hay cách dùng sẽ nói lên sự khác biệt lớn nhất của hai thằng này, nào cùng xem qua ví dụ.

Chúng ta cần group tên của nhân vật theo chữ cái đầu tiên trong các tên đó.

var names = new string[] { "Alex", "George", "Alfredo", "Bo", "Greg", "Maxim" };
 
var querySyntax = from name in names
    group name by name[0];
 
var methodSyntax = names
    .GroupBy(name => name[0]);
 
foreach (var pair in querySyntax)
{
    var groupedName = string.Join(", ", pair.ToList());
    Console.WriteLine($"Key = {pair.Key} Names = {groupedName}");
}

// output:
// Key = A Names = Alex, Alfredo
// Key = G Names = George, Greg
// Key = B Names = Bo
// Key = M Names = Maxim

foreach (var pair in methodSyntax)
{
    var groupedName = string.Join(", ", pair.ToList());
    Console.WriteLine($"Key = {pair.Key} Names = {groupedName}");
}

// output:
// Key = A Names = Alex, Alfredo
// Key = G Names = George, Greg
// Key = B Names = Bo
// Key = M Names = Maxim

Thoạt nhìn thì không có vấn đề gì cho lắm nhưng GroupBy của linq method không chỉ có thế, nó còn support nhiều cái hơn nữa, ở phạm vi đơn giản nếu sử dụng nó thì sẽ hơi có chút khó hiểu khi đọc code mà thôi.

Để group nhiều điều kiện thì cũng tương tự:

var people = new Person[]
{
    new Person() { Age = 20, Income = 4000, Name = "Viktor" },
    new Person() { Age = 30, Income = 8000, Name = "Merry" },
    new Person() { Age = 30, Income = 7000, Name = "Happy" },
    new Person() { Age = 20, Income = 4000, Name = "Tony Stark" },
    new Person() { Age = 30, Income = 6000, Name = "Madarin" },
    new Person() { Age = 30, Income = 5500, Name = "Pepper" },
};
 
var querySyntax = from person in people
    group person by new { person.Age, person.Income } into grouped
    orderby grouped.Key.Age, grouped.Key.Income
    select grouped;

var methodSyntax = people
    .GroupBy(person => new { person.Age, person.Income })
    .OrderBy(person => person.Key.Age)
    .ThenBy(person => person.Key.Income);
    
foreach (var person in querySyntax)
{
    // Có thể select Name trong select linq query, vì select bắt buộc phải có khi viết theo cú pháp này
    // object trả về là grouped object này ở dạng { Key, person }
    var named =  string.Join(", ", person.Select(x => x.Name).ToList()); 
    Console.WriteLine($"Key = {person.Key} Names = {named}");
}

foreach (var person in methodSyntax)
{
    // Nếu không có select trong query thì phải select khi dùng
    // object trả về sẽ ở dạng { Key, person }
    var named =  string.Join(", ", person.Select(x => x.Name).ToList()); 
    Console.WriteLine($"Key = {person.Key} Names = {named}");
}

// output
// Key = { Age = 20, Income = 4000 } Names = Viktor, Tony Stark
// Key = { Age = 30, Income = 5500 } Names = Pepper
// Key = { Age = 30, Income = 6000 } Names = Madarin
// Key = { Age = 30, Income = 7000 } Names = Happy
// Key = { Age = 30, Income = 8000 } Names = Merry

Cũng hơn lằng nhằng nhưng với linq query khi group với nhiều giá trị bắt buộc phải có into vào giá trị nào đấy, bên cạnh đó phải có thêm select. Linq method thì có thể nhận nhiều arguments hơn, cho phép "tùy chỉnh" lại kết quả bằng cách truyền một delegate vào tham số thứ hai.

Ở khoản này có vẻ linq query sẽ giúp nhìn clear hơn so với linq method, cơ mà việc đặt tên biến chỗ into cũng hơi hại não 😄.

Phép join thì sao

Đây là mấu chốt giúp linq query được nhiều developer chọn hơn so với linq method, cú pháp như sql lun OMG.

var categories = new Category[]
{
    new Category() { Name = "Toys", Id = 1 },
    new Category() { Name = "Electrical", Id = 2 },
};
var products = new Product[]
{
    new Product() { Name = "Toy car", CategoryId = 1 },
    new Product() { Name = "Blender", CategoryId = 2 },
    new Product() { Name = "Washing machine", CategoryId = 2 },
    new Product() { Name = "Bear", CategoryId = 1 },
};
 
var querySyntax =
    from product in products
    join category in categories on product.CategoryId equals category.Id
    select new
    {
        ProductName = product.Name,
        Category = category.Name
    };
 
var methodSyntax = products.Join(categories,
    product => product.CategoryId,
    category => category.Id,
    (product, category) => new
    {
        ProductName = product.Name,
        Category = category.Name
    });

foreach (var item in methodSyntax)
{
    Console.WriteLine($"ProductName = {item.ProductName}, Category = {item.Category}");
}
 
// result:
// ProductName = Toy car, Category = Toys
// ProductName = Blender, Category = Electrical
// ProductName = Washing machine, Category = Electrical
// ProductName = Bear, Category = Toys

Như các bạn đã thấy, khi join linq method cần tới mấy cái parameter để truyền vào, đầu tiên là bảng cần join, key cần map, và delegate trả về kết quả. Còn với linq query thì sao, mà thôi khỏi nói nữa, quá giống với sql rồi còn gì. Cho nên chỗ này mình sẽ bảo linq query là tốt nhất 😃) tốt về khía cạnh code dễ đọc và dễ viết.

Tóm lại

Với những cái đơn giản hay cần filter nhanh gọn lẹ dùng tới Where, Select, SelectMany, OrderBy ... thì mình thấy linq method ngon lành cành đào. Nếu hiểu biểu thức lamda bên C# hay rành rọt arrow function bên JS thì nhìn vào phát là hiểu ngay. Nói chung bài viết này không có nói ai tốt hơn ai, hay khuyên bảo bỏ cái này theo cái kia. Mục đích cung cấp bạn vài lợi thế của cú pháp kiểu query để bạn có cái nhìn rõ ràng. Có nhiều lập trình viên yêu thích cú pháp linq method lắm nhé, với code linq method công nhận cái đầu bá thiệt.

Một vài case gợi ý nên sử dụng linq query:

  • Dùng phép join
  • Query từ nhiều source
  • Cần query con ròi lưu lại vào biến để query cái khác sau (dùng từ khóa let)

All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí