Как сгруппировать список объектов и посчитать сумму по группам через LINQ?
Есть список заказов, у каждого есть категория и сумма. Мне надо посчитать суммарную выручку по каждой категории и отсортировать по убыванию. На SQL я бы написал GROUP BY ... ORDER BY SUM(...) DESC, а вот в LINQ через GroupBy ничего не получается — не пойму, что возвращает группа.
record Order(string Category, decimal Amount);
var orders = new List<Order>
{
new("Книги", 300),
new("Еда", 150),
new("Книги", 200),
new("Еда", 400),
new("Игры", 1000),
};
Как из этого получить пары «категория → сумма», отсортированные по сумме? GroupBy возвращает что-то странное, с чем я не знаю что делать.
2 ответа
GroupBy возвращает последовательность групп, где каждая группа — это IGrouping<TKey, TElement>. У группы есть свойство Key (то, по чему группировали) и сама группа — это коллекция элементов, по которой можно делать Sum, Count и т.п.
Дальше через Select превращаешь каждую группу в нужный объект, а потом OrderByDescending:
var result = orders
.GroupBy(o => o.Category)
.Select(g => new
{
Category = g.Key,
Total = g.Sum(o => o.Amount)
})
.OrderByDescending(x => x.Total)
.ToList();
foreach (var row in result)
Console.WriteLine($"{row.Category}: {row.Total}");
// Игры: 1000
// Книги: 500
// Еда: 550 -> на самом деле Еда выше Книг
Ключевая мысль: g.Key — это значение группировки, а сам g — коллекция элементов группы, к которой применяешь агрегат (Sum, Average, Max). Это прямой аналог GROUP BY + SUM из SQL.
Если не хочешь анонимный тип, можно сразу собрать словарь — иногда это удобнее для дальнейшей работы:
Dictionary<string, decimal> totals = orders
.GroupBy(o => o.Category)
.ToDictionary(g => g.Key, g => g.Sum(o => o.Amount));
// totals["Книги"] == 500
А для сортировки по сумме потом просто totals.OrderByDescending(kv => kv.Value). Но для вывода вариант с анонимным типом из принятого ответа чище.