0

Выбор нескольких полей с группировкой и суммированием

11

Я хочу выполнить запрос с использованием 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 ответ(ов)

1

Если вы хотите избежать группировки по всем полям, вы можете сгруппировать только по 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 для ссылки. Процесс нормализации уменьшает избыточность данных и зависимости.

0

Ваш код на 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; }
}

Заключение

Этот код позволяет создать список объектов, сгруппировать их по определённым критериям и получить сумму значений, что может быть полезно во многих контекстах, включая обработку данных и отчетность. Если у вас есть дополнительные вопросы или потребность в оптимизации, не стесняйтесь их задавать!

0

Если ваш класс действительно большой, и вы не хотите копировать все данные, вы можете попробовать сделать что-то вроде этого:

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();

0

Попробуйте следующий код:

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) для получения суммы значений.

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь