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 повинен мати кількість = 2!

Я спробував імітувати це в простому консольному додатку, але там я отримав такий результат:

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

Продукт1: повинен бути вказаний лише один раз ... Код вищезазначеного можна знайти на пастібіні: http://pastebin.com/cNHTBSie

Відповіді:


285

Я не розумію, звідки береться перший "результат із зразковими даними", але проблема в консольному додатку полягає в тому, що ви використовуєте 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
Чому б не згрупуватися за кодом продукту та назвою? Щось таке: .GroupBy (l => новий {l.ProductCode, l.Name}) та використовуйте ProductName = c.Key.Name,
Кирило Бестем'янов

@KirillBestemyanov: Так, звичайно, інший варіант.
Джон Скіт

Гаразд, здається, що моя колекція насправді була справжньою обгорткою навколо справжньої колекції .. Стріляй мені .. Добре, що я сьогодні практикував Linq :)
ThdK

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.