Linq: GroupBy, Sum и Count


133

У меня есть коллекция товаров

public class Product {

   public Product() { }

   public string ProductCode {get; set;}
   public decimal Price {get; set; }
   public string Name {get; set;}
}

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

public class ResultLine{

   public ResultLine() { }

   public string ProductName {get; set;}
   public string Price {get; set; }
   public string Quantity {get; set;}
}

Поэтому я использую GroupBy для группировки по ProductCode, затем вычисляю сумму, а также подсчитываю количество записей для каждого кода продукта.

Вот что у меня есть на данный момент:

List<Product> Lines = LoadProducts();    
List<ResultLine> result = Lines
                .GroupBy(l => l.ProductCode)
                .SelectMany(cl => cl.Select(
                    csLine => new ResultLine
                    {
                        ProductName =csLine.Name,
                        Quantity = cl.Count().ToString(),
                        Price = cl.Sum(c => c.Price).ToString(),
                    })).ToList<ResultLine>();

По какой-то причине сумма сделана правильно, но счет всегда равен 1.

Данные Sampe:

List<CartLine> Lines = new List<CartLine>();
            Lines.Add(new CartLine() { ProductCode = "p1", Price = 6.5M, Name = "Product1" });
            Lines.Add(new CartLine() { ProductCode = "p1", Price = 6.5M, Name = "Product1" });
            Lines.Add(new CartLine() { ProductCode = "p2", Price = 12M, Name = "Product2" });

Результат с образцами данных:

Product1: count 1   - Price:13 (2x6.5)
Product2: count 1   - Price:12 (1x12)

У продукта 1 должно быть count = 2!

Я попытался смоделировать это в простом консольном приложении, но получил следующий результат:

Product1: count 2   - Price:13 (2x6.5)
Product1: count 2   - Price:13 (2x6.5)
Product2: count 1   - Price:12 (1x12)

Продукт1: следует указывать только один раз ... Код для вышеуказанного можно найти на pastebin: http://pastebin.com/cNHTBSie

Ответы:


286

Я не понимаю, откуда берется первый «результат с образцами данных», но проблема в консольном приложении заключается в том, что вы используете его SelectManyдля просмотра каждого элемента в каждой группе .

Я думаю, ты просто хочешь:

List<ResultLine> result = Lines
    .GroupBy(l => l.ProductCode)
    .Select(cl => new ResultLine
            {
                ProductName = cl.First().Name,
                Quantity = cl.Count().ToString(),
                Price = cl.Sum(c => c.Price).ToString(),
            }).ToList();

Использование First()здесь для получения названия продукта предполагает, что каждый продукт с одинаковым кодом продукта имеет одинаковое название. Как отмечалось в комментариях, вы можете группировать по названию продукта, а также по коду продукта, что даст одинаковые результаты, если имя всегда одинаково для любого данного кода, но, очевидно, генерирует лучший SQL в EF.

Я также полагаю , что вы должны изменить Quantityи Priceсвойство быть intи decimalтипов соответственно - почему использование строки свойство для данных , которые явно не текстуальные?


Хорошо, мое консольное приложение работает. Спасибо, что указали мне использовать First () и не использовали SelectMany. ResultLine на самом деле является ViewModel. Цена будет отформатирована со знаком валюты. Вот почему мне нужно, чтобы это была строка. Но я могу изменить количество на int .. Я посмотрю, может ли это также помочь для моего сайта. Я дам Вам знать.
— ThdK

6
@ThdK: Нет, вы также должны сохранить Priceв десятичном формате, а затем изменить его форматирование. Следите за чистотой представления данных и переходите к представлению презентации только в последний момент.
— Джон Скит

4
Почему бы не сгруппировать по ProductCode и Name? Примерно так: .GroupBy (l => new {l.ProductCode, l.Name}) и используйте ProductName = c.Key.Name,
— Кирилл Бестемьянов

@KirillBestemyanov: Да, это еще один вариант, конечно.
— Джон Скит

1
Этот пост дает высокие результаты при поиске информации об агрегированных результатах с использованием group by, но я хотел бы предостеречь при его использовании против EntityFramework. First / FirstOrDefault приведет к тому, что EF будет создавать вложенные выборки, которые могут иметь серьезные последствия для производительности. Предложение Кирилла об использовании GroupBy генерирует ожидаемый SQL.
— ShaneH

27

Следующий запрос работает. Он использует каждую группу для выбора вместо SelectMany. SelectManyработает над каждым элементом из каждой коллекции. Например, в вашем запросе у вас есть результат 2 коллекций. SelectManyполучает все результаты, всего 3, вместо каждой коллекции. Следующий код работает с каждым IGroupingв выбранной части, чтобы ваши агрегированные операции работали правильно.

var results = from line in Lines
              group line by line.ProductCode into g
              select new ResultLine {
                ProductName = g.First().Name,
                Price = g.Sum(pc => pc.Price).ToString(),
                Quantity = g.Count().ToString(),
              };

2

иногда вам нужно выбрать некоторые поля FirstOrDefault()или singleOrDefault()вы можете использовать следующий запрос:

List<ResultLine> result = Lines
    .GroupBy(l => l.ProductCode)
    .Select(cl => new Models.ResultLine
            {
                ProductName = cl.select(x=>x.Name).FirstOrDefault(),
                Quantity = cl.Count().ToString(),
                Price = cl.Sum(c => c.Price).ToString(),
            }).ToList();

1
не могли бы вы объяснить, почему иногда мне нужно использовать FirstOrDefault() or singleOrDefault () `?
— Shanteshwar Inde

@ShanteshwarInde First () и FirstOrDefault () получает первый объект в серии, тогда как Single () и SingleOrDefault () ожидают только 1 от результата. Если Single () и SingleOrDefault () обнаруживают, что в результирующем наборе или в результате предоставленного аргумента присутствует более 1 объекта, он выдаст исключение. При использовании вы используете первый только тогда, когда хотите, возможно, образец серии и другие объекты не важны для вас, тогда как вы используете последний, если вы ожидаете только один объект и делаете что-то, если есть более одного результата , вроде зарегистрируйте ошибку.
— Кристиан Нерона,
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.