2012-04-26 6 views
6

Supongamos que tengo una clase que representa las líneas de órdenes, por ejemplo¿Cómo aplana una consulta de Linq después de usar la agrupación doble?

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

y una lista de líneas, por ejemplo

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

Busco para recuperar todas las líneas en las que el código de cliente tiene más de una factura. Para hacer esto, estoy ante todo agrupando por Código, No y Fecha y luego agrupando eso por Código de cliente y para cualquier cliente que tenga dos o más registros, estoy seleccionando todos menos el primer registro.

así:

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

consulta1 ahora contiene los registros correctos, pero envuelto en el interior IGrouping y estoy teniendo problemas obtiene los resultados como una List<Line>

me trataron query1.SelectMany (r = > r.Results) .ToList();

pero esto todavía me deja con IGrouping y ahí es donde estoy atascado.

que podría recurrir a la bucles for anidados como en

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

pero hay una manera mejor/LINQ?

La salida real debería ser de cuatro líneas como en

// 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

que cambiaría 'results2.ToList() OrdenarPor (i => i.Key.Date) Saltee (1) .ToList() 'to' results2.OrderBy (i => i.Key.Date) .Skip (1) .ToList() '. No hay ninguna razón para la primera llamada 'ToList()'. – phoog

+0

@phoog He eliminado el primer ToList() ya que tiene razón en que no es necesario, pero tampoco afecta los resultados, así que todavía tengo mi problema original. – sgmoore

+0

sí, publiqué eso como un comentario porque no responde su pregunta. También publiqué una respuesta a continuación. – phoog

Respuesta

9

vas a patear a ti mismo:

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 

El regreso de query1 es un enumerable (quité sus listas) de IGrouping y IGrouping es en sí mismo un enumerable, por lo que podemos aplanarlo directamente.

Ver aquí: http://mtaulty.com/CommunityServer/blogs/mike_taultys_blog/archive/2007/09/28/9836.aspx

Editar: Recordaba también he simplificado el código:

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

Si la consulta1.SelectMany (q => q) había funcionado antes de que simplificara mi consulta, definitivamente me habría merecido una buena patada dura, pero para ser honesto, no creo que lo hubiera conseguido por mi cuenta. Así que gracias. – sgmoore

+0

Sí, olvidé que había simplificado la consulta. Antes de hacerlo, @phoog tenía la respuesta correcta. Independientemente del truco fue darse cuenta de que 'IGrouping ' implementa 'IEnumberable '. – yamen

3

Prueba esto:

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, porque outerGroup.Results es 'IEnumerable >', donde 'TKey' es el tipo anónimo que define la clave. Eso fue, creo, el problema inicial del OP. Yamen mostró cómo simplificar la consulta de aplanamiento simplificando 'query1', pero si dejamos' query1' como está, tenemos que tener dos llamadas SelectMany, o tres cláusulas 'from'. – phoog

9

este código:

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

Es lo mismo que:.

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