2013-05-13 22 views
72

Ho una collezione di prodottiLinq: GroupBy, Somma e Conte

public class Product { 

    public Product() { } 

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

Ora voglio gruppo la collezione in base al codice del prodotto e restituire un oggetto che contiene il nome, il numero o prodotti per ciascun codice e il prezzo totale per ogni prodotto.

public class ResultLine{ 

    public ResultLine() { } 

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

Così ho utilizzare un GroupBy al gruppo da CodiceProdotto, poi a calcolare la somma e anche contare il numero di record per ciascun codice di prodotto.

Questo è quello che ho finora:

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

Per qualche ragione, la somma viene eseguita correttamente, ma il conteggio è sempre 1. dati

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

Risultato con dati di esempio:

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

Prodotto 1 dovrebbe avere count = 2!

Ho cercato di simulare questo in una semplice applicazione console, ma non ho ottenuto il seguente risultato:

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

Product1: dovrebbe essere elencato solo una volta ... Il codice per quanto sopra può essere trovato su pastebin : http://pastebin.com/cNHTBSie

risposta

167

non capisco dove il primo "risultato con dati di esempio" proviene, ma il problema nella console app è che si sta utilizzando SelectMany guardare ogni elemento in ogni gruppo.

Penso che si desidera semplicemente:

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

L'uso di First() qui per ottenere il nome del prodotto presuppone che tutti i prodotti con lo stesso codice prodotto ha lo stesso nome del prodotto. Come notato nei commenti, è possibile raggruppare in base al nome del prodotto e al codice del prodotto, che darà gli stessi risultati se il nome è sempre lo stesso per un dato codice, ma apparentemente genera un migliore SQL in EF.

Vorrei anche suggerire che si dovrebbe cambiare il Quantity e Price proprietà da int e decimal tipi rispettivamente - perché usare una proprietà di stringa per i dati che non è chiaramente testuali?

+0

Ok, la mia app della console funziona. Grazie per avermi contattato per utilizzare First() e lasciare SelectMany. Il ResultLine è in realtà un ViewModel.Il prezzo verrà formattato con il simbolo di valuta. Ecco perché ho bisogno che sia una stringa. Ma potrei cambiare la quantità in int .. Vedrò ora se questo può anche aiutare per il mio sito web. Ti farò sapere. – ThdK

+5

@ThdK: No, si dovrebbe mantenere anche "Prezzo" come decimale, e quindi modificare il modo in cui lo si formatta. Mantieni la rappresentazione dei dati pulita e passa alla visualizzazione della presentazione solo all'ultimo momento. –

+4

Perché non raggruppare in base al codice prodotto e al nome? Qualcosa del genere: .GroupBy (l => new {l.ProductCode, l.Name}) e usa ProductName = c.Key.Name, –

13

La seguente query funziona. Utilizza ciascun gruppo per selezionare invece di SelectMany. SelectMany funziona su ogni elemento di ogni raccolta. Ad esempio, nella tua query hai un risultato di 2 collezioni. SelectMany ottiene tutti i risultati, per un totale di 3, anziché ogni raccolta. Il seguente codice funziona su ogni IGrouping nella porzione selezionata per far funzionare correttamente le operazioni di aggregazione.

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