2012-04-26 5 views
6

Supponiamo che io sono una classe che rappresenta righe d'ordine, ad esempioCome si appiattisce una query Linq dopo aver usato il doppio raggruppamento?

public class Line 
{ 
    public string Code ; 
    public string No ; // Invoice Number 
    public DateTime Date ; 
    public string Product ; 
    public decimal Quantity ; 
} 

e un elenco di linee, ad esempio

List<Line> myList = new List<Line>(); 
myList.Add(new Line() { Code = "ABC001", No = "1001" ,Date = new DateTime(2012,4,1) , Product = "X", Quantity= 1m}); 
myList.Add(new Line() { Code = "ABC001", No = "1001" ,Date = new DateTime(2012,4,1) , Product = "Y", Quantity= 1m}); 

myList.Add(new Line() { Code = "ABC002", No = "1002" ,Date = new DateTime(2012,4,2) , Product = "X", Quantity= 1m}); 
myList.Add(new Line() { Code = "ABC002", No = "1002" ,Date = new DateTime(2012,4,2) , Product = "Y", Quantity= 1m}); 
myList.Add(new Line() { Code = "ABC002", No = "1003" ,Date = new DateTime(2012,4,3) , Product = "Z", Quantity= 1m}); 
myList.Add(new Line() { Code = "ABC002", No = "1004" ,Date = new DateTime(2012,4,4) , Product = "X", Quantity= 1m}); 

myList.Add(new Line() { Code = "ABC003", No = "1005" ,Date = new DateTime(2012,4,4) , Product = "X", Quantity= 1m}); 
myList.Add(new Line() { Code = "ABC003", No = "1006" ,Date = new DateTime(2012,4,4) , Product = "X", Quantity= 1m}); 
myList.Add(new Line() { Code = "ABC003", No = "1006" ,Date = new DateTime(2012,4,4) , Product = "Y", Quantity= 1m}); 

Sto cercando di recuperare tutte le righe in cui il codice cliente ha più di una fattura. Per fare questo, sono prima di tutto raggruppando per Codice, No e Data e poi raggruppando quello per Codice cliente e per tutti i clienti che hanno due o più record, sto selezionando tutto tranne il primo record.

Come così:

var query1 = 
    (from r in myList 
     group r by new { r.Code , r.No , r.Date } into results 
     group results by new { results.Key.Code } into results2 
     where results2.Count() > 1 
     select new 
     { 
      results2.Key.Code , 
      Count = results2.Count(), 
      Results = results2.OrderBy(i=>i.Key.Date).Skip(1).ToList() 
      // Skip the first invoice 
     } 
    ).ToList(); 

query1 contiene ora i record corretti, ma avvolto dentro IGrouping e sto avendo difficoltà ottiene i risultati come un List<Line>

ho cercato query1.SelectMany (r = > r.Results) ToList();

ma questo mi lascia ancora con IGrouping ed è lì che sono bloccato.

ho potuto ricorrere al cicli for innestati come in

List<Line> output = new List<Line>(); 
foreach (var r1 in query1) 
{ 
    foreach(var r2 in r1.Results) 
     foreach(var r3 in r2) 
      output.Add(r3);  
} 

ma c'è un/modo migliore Linq?

L'uscita effettiva dovrebbe essere quattro linee come in

// Code No Date    Product Quantity 
// ABC002 1003 03/04/2012 00:00:00 Z 1 
// ABC002 1004 04/04/2012 00:00:00 X 1 
// ABC003 1006 04/04/2012 00:00:00 X 1 
// ABC003 1006 04/04/2012 00:00:00 Y 1 
+1

vorrei cambiare 'results2.ToList() OrderBy (i => i.Key.Date) .Skip (1) .ToList() 'a' results2.OrderBy (i => i.Key.Date) .Skip (1) .ToList() '. Non c'è motivo per la prima chiamata "ToList()". – phoog

+0

@phoog Ho rimosso la prima ToList() perché è corretta in quanto non è necessaria, ma non influenza anche i risultati, quindi ho ancora il mio problema originale. – sgmoore

+0

sì, l'ho postato come commento perché non risponde alla tua domanda. Ho anche pubblicato una risposta qui sotto. – phoog

risposta

9

si sta andando a calci da soli:

query1.SelectMany(q => q); 

ABC002 1003 3/04/2012 12:00:00 AM Z 1 
ABC002 1004 4/04/2012 12:00:00 AM X 1 
ABC003 1006 4/04/2012 12:00:00 AM X 1 
ABC003 1006 4/04/2012 12:00:00 AM Y 1 

Il ritorno da query1 è un enumerabile (ho rimosso le vostre liste) di IGrouping e IGrouping è a sua volta un enumerabile, quindi possiamo semplicemente appiattirlo direttamente.

vedere qui: http://mtaulty.com/CommunityServer/blogs/mike_taultys_blog/archive/2007/09/28/9836.aspx

Edit: ricordato che anche semplificato il codice:

var query1 = 
(from r in myList 
    group r by new { r.Code , r.No , r.Date } into results 
    group results by new { results.Key.Code } into results2 
    where results2.Count() > 1 
    from result in results2.OrderBy(i=>i.Key.Date).Skip(1) 
    select result 
); 
+0

Se la query1.SelectMany (q => q) avesse funzionato prima di semplificare la mia interrogazione, allora avrei sicuramente meritato un buon calcio, ma onestamente non credo che l'avrei ottenuto da solo. Quindi grazie. – sgmoore

+0

Sì, ho dimenticato di aver semplificato la query. Prima che lo facessi, @phoog aveva la risposta giusta. Indipendentemente dal trucco stava realizzando che 'IGrouping ' implementa 'IEnumberable '. – yamen

3

Prova questa:

var flattenedLines = from outerGroup in query1 
        from innerGroup in outerGroup.Results 
        from line in innerGroup 
        select line; 

O

var flattenedLines = query1 
    .SelectMany(outerGroup => outerGroup.Results, (outerGroup, innerGroup) => innerGroup) 
    .SelectMany(x => x); 
+0

@NiklasB. no, perché outerGroup.Results è 'IEnumerable >', dove 'TKey' è il tipo anonimo che definisce la chiave. Questo era, penso, il problema iniziale dell'OP. Yamen ha mostrato come semplificare la query di appiattimento semplificando 'query1', ma se lasciamo' query1' così com'è, dobbiamo avere due chiamate SelectMany o tre clausole 'from'. – phoog

9

Questo codice:

List<Line> output = new List<Line>(); 
foreach (var r1 in query1) 
    foreach(var r2 in r1.Results)   
    foreach(var r3 in r2) 
     output.Add(r3); 

è la stessa cosa come:.

var q2 = from r1 in query1 
     from r2 in r1.Results 
     from r3 in r2 
     select r3; 
var output = q2.ToList(); 
Problemi correlati