Выбор нескольких полей с группировкой и суммированием
Я хочу выполнить запрос с использованием LINQ (список объектов), но не знаю, как это сделать. Я могу сгруппировать и подсчитать сумму, но не могу выбрать остальные поля.
Вот пример данных:
ID Value Name Category
1 5 Name1 Category1
1 7 Name1 Category1
2 1 Name2 Category2
3 6 Name3 Category3
3 2 Name3 Category3
Мне нужно сгруппировать по ID, вычислить сумму по полю Value и вернуть все поля в следующем формате:
ID Value Name Category
1 12 Name1 Category1
2 1 Name2 Category2
3 8 Name3 Category3
Как я могу это реализовать с помощью LINQ?
4 ответ(ов)
Если вы хотите избежать группировки по всем полям, вы можете сгруппировать только по Id
:
data.GroupBy(d => d.Id)
.Select(
g => new
{
Key = g.Key,
Value = g.Sum(s => s.Value),
Name = g.First().Name,
Category = g.First().Category
});
Но этот код предполагает, что для каждого Id
применимы одни и те же Name
и Category
. Если это так, вам стоит рассмотреть возможность нормализации, как предлагает @Aron. Это будет означать, что нужно оставить Id
и Value
в одном классе, а Name
, Category
(и любые другие поля, которые совпадают для одного и того же Id
) переместить в другой класс, при этом также сохранив Id
для ссылки. Процесс нормализации уменьшает избыточность данных и зависимости.
Ваш код на C# выполняет агрегацию значений в массиве объектов Foo
и выводит результаты на консоль. Давайте рассмотрим подробнее, что здесь происходит.
Основная идея кода
Вы создаете массив объектов Foo
, каждый из которых имеет свойства Id
, Value
, Name
и Category
. В вашем примере вы инициализируете свойства Name
и Category
внутри цикла foreach
:
foreach(var x in foos)
{
x.Name = "Name" + x.Id;
x.Category = "Category" + x.Id;
}
Затем вы используете LINQ для группировки значений по Id
, Name
и Category
, и суммируете значения Value
в каждой группе:
var result = from x in foos
group x.Value by new { x.Id, x.Name, x.Category} into g
select new { g.Key.Id, g.Key.Name, g.Key.Category, Value = g.Sum() };
Вывод результата
Результат агрегации будет представлен в виде анонимного типа, содержащего Id
, Name
, Category
и сумму значений Value
для каждой группы. Однако, чтобы выводить результат в консоль, вам нужно будет перебрать result
и вывести его поэлементно, например так:
foreach (var item in result)
{
Console.WriteLine($"Id: {item.Id}, Name: {item.Name}, Category: {item.Category}, Total Value: {item.Value}");
}
Полный пример
Вот как будет выглядеть полный пример с учетом вывода:
void Main()
{
var foos = new []
{
new Foo { Id = 1, Value = 5},
new Foo { Id = 1, Value = 7},
new Foo { Id = 2, Value = 1},
new Foo { Id = 3, Value = 6},
new Foo { Id = 3, Value = 2},
};
foreach(var x in foos)
{
x.Name = "Name" + x.Id;
x.Category = "Category" + x.Id;
}
var result = from x in foos
group x.Value by new { x.Id, x.Name, x.Category} into g
select new { g.Key.Id, g.Key.Name, g.Key.Category, Value = g.Sum() };
// Выводим результат
foreach (var item in result)
{
Console.WriteLine($"Id: {item.Id}, Name: {item.Name}, Category: {item.Category}, Total Value: {item.Value}");
}
}
public class Foo
{
public int Id { get; set; }
public int Value { get; set; }
public string Name { get; set; }
public string Category { get; set; }
}
Заключение
Этот код позволяет создать список объектов, сгруппировать их по определённым критериям и получить сумму значений, что может быть полезно во многих контекстах, включая обработку данных и отчетность. Если у вас есть дополнительные вопросы или потребность в оптимизации, не стесняйтесь их задавать!
Если ваш класс действительно большой, и вы не хотите копировать все данные, вы можете попробовать сделать что-то вроде этого:
l.GroupBy(x => x.id)
.Select(x => {
var ret = x.First();
ret.value = x.Sum(xt => xt.value);
return ret;
}).ToList();
Однако с великой силой приходит и великая ответственность. Вам нужно быть осторожным. Строка ret.value = x.Sum(xt => xt.value)
изменит вашу оригинальную коллекцию, так как вы передаёте ссылку, а не создаёте новый объект. Если вы хотите этого избежать, вам необходимо добавить в свой класс метод Clone
, например MemberwiseClone
(но будьте осторожны: это создаст поверхностную копию). После этого просто замените строку на: var ret = x.First().Clone();
Попробуйте следующий код:
var objList = new List<SampleObject>();
objList.Add(new SampleObject() { ID = 1, Value = 5, Name = "Name1", Category = "Catergory1" });
objList.Add(new SampleObject() { ID = 1, Value = 7, Name = "Name1", Category = "Catergory1" });
objList.Add(new SampleObject() { ID = 2, Value = 1, Name = "Name2", Category = "Catergory2" });
objList.Add(new SampleObject() { ID = 3, Value = 6, Name = "Name3", Category = "Catergory3" });
objList.Add(new SampleObject() { ID = 3, Value = 2, Name = "Name3", Category = "Catergory3" });
var newList = from val in objList
group val by new { val.ID, val.Name, val.Category } into grouped
select new SampleObject() { ID = grouped.Key.ID, Value = grouped.Sum(v => v.Value), Name = grouped.Key.Name, Category = grouped.Key.Category };
Чтобы проверить результат в LINQPad, добавьте следующее:
newList.Dump();
Обратите внимание на исправления: необходимо использовать grouped.Key
для доступа к полям группировки и grouped.Sum(v => v.Value)
для получения суммы значений.
Как установить заголовок Content-Type для запроса HttpClient?
Таймаут при использовании Linq-to-SQL
Обрезка строки запроса и возврат чистого URL в C# ASP.NET
Как запустить модульные тесты на платформе x64?
"Ошибка при выполнении криптографической операции при расшифровке cookie Forms"