2013-05-13 15 views
72

Mam kolekcję produktówLINQ: GroupBy, Sum i liczyć

public class Product { 

    public Product() { } 

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

Teraz chcę grupy kolekcję na podstawie kodu produktu i powrócić obiekt zawierający nazwę, numer lub produktów, dla każdego kodu i łączna cena dla każdego produktu.

public class ResultLine{ 

    public ResultLine() { } 

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

Więc używam GroupBy do grupy, kodu produktu, a następnie obliczyć sumę, a także policzyć liczbę rekordów dla każdego kodu produktu.

To, co mam tak daleko:

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

Z jakiegoś powodu suma jest zrobione poprawnie, ale liczba jest zawsze 1.

sampe dane:

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" }); 

Wynik z przykładowymi danymi:

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

Produkt 1 powinien mieć wartość = 2!

próbowałem symulować to w prostej aplikacji konsoli, ale nie mam następujący wynik:

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

Produkt1: powinny być wymienione tylko raz ... Kod na powyższe można znaleźć na pastebin : http://pastebin.com/cNHTBSie

Odpowiedz

167

nie rozumiem, gdzie pierwszy „wynikają z przykładowych danych” pochodzi, ale problem w aplikacji konsoli jest to, że używasz SelectMany patrzeć każdy element w każdej grupie.

myślę po prostu chcesz:

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

Zastosowanie First() tutaj, aby uzyskać nazwa produktu zakłada, że ​​każdy produkt z takim samym kodem produktu ma tę samą nazwę produktu. Jak zauważono w komentarzach, możesz grupować według nazwy produktu, a także kodu produktu, który da takie same wyniki, jeśli nazwa jest zawsze taka sama dla danego kodu, ale najwyraźniej generuje lepszy SQL w EF.

Chciałbym również sugerują, że należy zmienić Quantity i Price Właściwości być int i decimal typy odpowiednio - dlaczego używać właściwości String dla danych, które nie są wyraźnie tekstowy?

+0

Ok moja aplikacja konsolowa działa. Dzięki za wskazanie mnie do użycia First() i pomiń SelectMany. ResultLine jest w rzeczywistości ViewModel.Cena zostanie sformatowana za pomocą znaku waluty. Właśnie dlatego potrzebuję tego jako struny. Ale mogę zmienić ilość na int .. Zobaczę teraz, czy to może również pomóc w mojej witrynie. Dam ci znać. – ThdK

+5

@ThdK: Nie, powinieneś zachować 'Cena' również w postaci dziesiętnej, a następnie zmienić sposób formatowania. Utrzymuj czystość reprezentacji danych i zmieniaj ją w widoku prezentacji w ostatnim możliwym momencie. –

+4

Dlaczego nie grupować według kodu produktu i nazwy? Coś w tym stylu: .GroupBy (l => new {l.ProductCode, l.Name}) i użyj ProductName = c.Key.Name, –

13

Następujące zapytanie działa. Używa każdej grupy do wyboru zamiast SelectMany. SelectMany działa na każdym elemencie z każdej kolekcji. Na przykład w zapytaniu masz wynik 2 kolekcji. SelectMany pobiera wszystkie wyniki, w sumie 3, zamiast każdej kolekcji. Poniższy kod działa na każdym kodzie IGrouping w wybranej części, aby zapewnić prawidłowe działanie operacji zagregowanych.

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