Группировка по нескольким полям с помощью LINQ в C#
Группировка по нескольким полям с помощью анонимного типа
В LINQ для группировки по нескольким полям нужно использовать анонимный тип (или кортеж в современных версиях C#). Ключ группировки должен быть одним объектом, который содержит все поля, по которым вы хотите группировать. Анонимный тип создается с помощью ключевого слова new без указания имени класса.
Вот правильное решение для вашей задачи:
var groupedSales = sales
.GroupBy(s => new { s.Store, s.Date })
.Select(g => new
{
Store = g.Key.Store,
Date = g.Key.Date,
TotalAmount = g.Sum(s => s.Amount)
});
foreach (var item in groupedSales)
{
Console.WriteLine($"{item.Store} | {item.Date.ToShortDateString()} | Сумма: {item.TotalAmount}");
}
Результат выполнения:
Магазин A | 01.01.2023 | Сумма: 250
Магазин B | 01.01.2023 | Сумма: 200
Магазин A | 02.01.2023 | Сумма: 120
Магазин B | 02.01.2023 | Сумма: 180
Как это работает
Разберем по шагам:
GroupBy(s => new { s.Store, s.Date })— создает группы, где ключом является анонимный объект с двумя свойствами:StoreиDate. Все записи с одинаковым магазином и датой попадают в одну группу.- Результат
GroupBy— это коллекция групп (типIGrouping<TKey, TElement>). Каждая группа содержит свойствоKey(наш анонимный объект) и коллекцию элементов этой группы. .Select(...)— проекция каждой группы в новый анонимный объект с нужными полями. Черезg.Key.Storeиg.Key.Dateмы получаем значения ключа, аg.Sum(s => s.Amount)суммирует все Amount внутри группы.
Альтернатива с кортежами (C# 7.0+)
Если вы используете C# 7.0 или новее, можно применить кортежи (ValueTuple). Это делает код более читаемым, особенно когда полей много:
var groupedSales = sales
.GroupBy(s => (s.Store, s.Date))
.Select(g => new
{
Store = g.Key.Store,
Date = g.Key.Date,
TotalAmount = g.Sum(s => s.Amount)
});
Обратите внимание: в GroupBy теперь используется (s.Store, s.Date) — это синтаксис кортежа. В остальном логика идентична.
Синтаксис запросов (query syntax)
Тот же результат можно получить с помощью синтаксиса запросов LINQ (напоминает SQL):
var groupedSales = from s in sales
group s by new { s.Store, s.Date } into g
select new
{
Store = g.Key.Store,
Date = g.Key.Date,
TotalAmount = g.Sum(s => s.Amount)
};
Этот вариант может быть привычнее для тех, кто знает SQL.
Важные моменты
- Сравнение ключей: Анонимные типы и кортежи автоматически реализуют
EqualsиGetHashCode, поэтому группировка работает корректно. Для собственных классов-ключей нужно переопределять эти методы. - Производительность: Группировка по нескольким полям незначительно медленнее, чем по одному, но для большинства задач это незаметно. Если данных очень много (миллионы записей), рассмотрите индексацию в базе данных.
- Порядок полей: В анонимном типе порядок свойств имеет значение.
new { s.Store, s.Date }иnew { s.Date, s.Store }— это разные ключи, хотя данные могут быть похожими. Будьте последовательны.
Теперь вы можете гибко группировать данные по любому количеству полей. Этот подход применяется не только в памяти (LINQ to Objects), но и в Entity Framework (LINQ to Entities) для генерации SQL-запросов с GROUP BY.
<<Использование вложенных GroupBy
Иногда, особенно в учебных целях, можно применить вложенную группировку: сначала сгруппировать по одному полю, а затем внутри каждой группы — по другому. Это менее эффективно, но наглядно показывает структуру данных.
var nestedGrouping = sales
.GroupBy(s => s.Store)
.Select(storeGroup => new
{
Store = storeGroup.Key,
DateGroups = storeGroup.GroupBy(s => s.Date)
.Select(dateGroup => new
{
Date = dateGroup.Key,
TotalAmount = dateGroup.Sum(s => s.Amount)
})
});
foreach (var store in nestedGrouping)
{
Console.WriteLine($"Магазин: {store.Store}");
foreach (var date in store.DateGroups)
{
Console.WriteLine($" {date.Date.ToShortDateString()}: {date.TotalAmount}");
}
}
Результат будет таким же, но в виде иерархии: магазин -> даты. Этот подход полезен, когда вам нужно сначала обработать данные по одному уровню группировки, а потом по другому. Однако для прямой агрегации (как в вашем случае — просто получить итоговые суммы) первый вариант с анонимным типом проще и быстрее.
Недостатки вложенного GroupBy:
- Больше кода и вложенных циклов.
- Меньшая производительность из-за двойной группировки.
- Сложнее использовать для дальнейшей обработки (например, для записи в таблицу "Магазин-Дата-Сумма").
Поэтому рекомендую основной подход из первого ответа, а вложенную группировку используйте только если вам действительно нужна иерархическая структура.
Ответов пока нет.
Войдите чтобы оставить ответ