• No se han encontrado resultados

Agrupaciones y combinaciones: group by y join into

In document c# linq (página 123-127)

Estas dos cláusulas se pueden usar para agrupar los datos de una consulta. Por un lado, group by nos permite crear grupos basados en la colección que indicamos en la consulta, mientras que join into nos permitirá combinar dos colecciones basadas en las claves que coincidan de éstas.

Para ser precisos, join into no agrupa datos, sino que los combina, al igual que hace join. De hecho, si buscamos equivalencias entre estas cláusulas de C# y las instrucciones de una consulta de T-SQL, la cláusula join equivaldría a INNER JOIN, mientras que join into se puede decir que equivale a LEFT JOIN.

Estas dos cláusulas que utiliza C# para las consultas de LINQ equivalen a los métodos extensores

GroupBy y GroupJoin.

Veamos por separado cómo podemos utilizarlas en nuestro código.

group by (GroupBy)

Por ejemplo, tenemos una serie de facturas y queremos agrupar las de cada cliente, de forma que el resultado de la consulta tenga cada uno de esos clientes y un grupo con las facturas que le correspon- den. Esto es lo que hace el código del listado 7.34.

var res1 = from f in facturas group f by f.Cliente;

Listado 7.34. Las facturas agrupadas por cada uno de los clientes

Cuando usamos la cláusula group by el resultado de la consulta es una colección del tipo

IGrouping<TKey, TElement> donde TKey es el tipo de datos por el que se agrupa (el indicado después de by) y TElement es una colección con cada uno de los elementos que estamos agrupando (el indicado después de group). En nuestro ejemplo la clave de cada elemento de la consulta resultante es un objeto del tipo Cliente (el tipo que devuelva la propiedad f.Cliente) y está representado por la propiedad Key. Por otro lado, los elementos de cada colección son las facturas que corresponden a ese cliente. Por tanto, para mostrar los datos debemos usar dos bucles anidados, tal como vemos en el listado 7.35.

foreach(var f in res1) { Console.WriteLine("{0} {1}", f.Key.Empresa, f.Key.País); foreach(var f1 in f) { Console.WriteLine(" {0} {1}", f1.Numero, f1.Fecha); } }

Como vemos en el código del listado 7.34, no estamos usando una cláusula select, esto es así porque las consultas de LINQ en C# deben acabar con select o group by.

Si quisiéramos hacer algún tipo de filtro, por ejemplo, ordenarlos, etc., podemos hacerlo después de

group by, en ese caso, agruparíamos los datos usando into seguido de la variable que contendrá la agrupación, pero en este caso sí que tendríamos que agregar una cláusula select (o bien usar otra instrucción group by). Un ejemplo de esto lo vemos en el listado 7.36, en el que ordenamos los ele- mentos por el país de cada cliente. Para mostrar estos datos, podemos usar un código similar al mos- trado en el listado 7.35.

var res2 = from f in facturas

group f by f.Cliente into g orderby g.Key.País

select g;

Listado 7.36. Podemos filtrar la agrupación realizada

Si necesitamos hacer alguna comprobación extra, por ejemplo, solo tener en cuenta los datos de las facturas que contengan más de 3 artículos, podemos utilizar los datos de la agrupación para realizar otra consulta y filtrar los datos que nos interesen. Eso lo vemos en el listado 7.37 en el que solo tenemos en cuenta las facturas con más de tres artículos, ordenamos los datos por el contenido de la propiedad País del cliente y además generamos un tipo anónimo con nombres de propiedades que nos den una pista más clara sobre el contenido, aunque en este caso, no sería necesario crear la propiedad

Cli, ya que la variable con los datos (g) sigue siendo del tipo IGrouping<K, E>, por tanto define una propiedad llamada Key que hace referencia al cliente al que pertenecen esas facturas, pero este “fo- llón” es más bien para que veamos que podemos complicarnos la vida de muchas formas, y como siempre, de nosotros depende que finalmente consigamos lo que queremos, algo que solo podremos hacer si tenemos las cosas claras y sabemos qué podemos hacer.

Para mostrar los datos de la consulta del listado 7.37, podemos hacerlo tal como vemos en el listado 7.38.

var res3 = from f in facturas

group f by f.Cliente into g from fv in g

where fv.Articulos.Count > 3 orderby g.Key.País descending

select new {

Cli = g.Key, Facts = g };

foreach(var f in res3) {

// Estos dos datos mostrarán lo mismo

Console.WriteLine(f.Facts.Key);

Console.WriteLine(f.Cli);

foreach(var f1 in f.Facts) {

Console.WriteLine(" {0} {1}", f1.Numero, f1.Fecha); }

}

Listado 7.38. Mostrar los datos de la consulta del listado 7.37 join into (GroupJoin)

La cláusula join into nos permite agrupar dos colecciones en una sola, pero siempre usando una com- paración de igualdad entre dos de los elementos de esas colecciones (ver lo comentado anteriormente sobre join). Por ejemplo, si queremos todas las facturas de todos los clientes, agrupadas por cliente, usando join into podríamos hacerlo tal como vemos en el listado 7.39.

var res1 = from c in clientes join f in facturas

on c.Codigo equals f.Cliente.Codigo into g

select new {

Cli = c, Facts = g,

TotalArts = g.Sum(n => n.Articulos.Count) };

Listado 7.39. Una consulta usando join into

Debido a que usamos join, la comparación debe ser de igualdad y siempre deben intervenir una pro- piedad de la variable usada en la colección a la izquierda de join (en este ejemplo la colección clientes) y otra en la colección indicada a la derecha (en este ejemplo, la colección facturas). El grupo generado será el de las facturas del cliente analizado, y en la cláusula select creamos un tipo de datos anónimo en el que incluimos el cliente, las facturas de ese cliente y por medio de la función de agregado Sum, obtenemos la suma total de todos los artículos de todas las facturas de ese cliente. Para mostrar los datos obtenidos en esta consulta, podemos usar el código del listado 7.40.

foreach(var f in res1) {

Console.WriteLine("{0}, {1}", f.Cli, f.TotalArts); foreach(var fv in f.Facts)

Console.WriteLine(" {0}", fv); }

Como ya comenté al principio de esta sección, las instrucciones usadas por C# para realizar estas acciones se equivalen con métodos extensores, y para que veamos qué código nos ahorramos de es- cribir, además de que comprobaremos que usar las instrucciones es más fácil de leer que usar los métodos equivalentes.

En el listado 7.41 tenemos el equivalente de la consulta join into del listado 7.39 y en el listado 7.42 vemos el código equivalente del listado 7.36, pero usando el método GroupBy en lugar de la cláusula

group by.

Pero si queremos saber lo que nos ahorramos de verdad al usar las instrucciones propias del lenguaje, podemos comparar los listados 7.37 (usando group by, where, orderby, etc.) y el correspondiente a los métodos extensores del listado 7.43 (recordemos que el compilador convertirá las cláusulas en llamadas a los métodos extensores equivalentes, por tanto, la mejor forma de saber qué es lo que el compilador genera, es inspeccionando el ejecutable resultante; que es casi lo que yo he hecho en este caso concreto, ya que no estaba dispuesto a perder la cabeza para conseguir el código equivalente).

var res2 = clientes.GroupJoin( facturas, c => c.Codigo, f => f.Cliente.Codigo, (c, f) => new { Cli = c, Facts = f,

TotalArts = f.Sum(n => n.Articulos.Count) });

Listado 7.41. Una consulta usando el método extensor GroupJoin (equivale al listado 7.39)

var res4 = facturas.GroupBy(f => f.Cliente).OrderBy(g => g.Key.País);

Listado 7.42. Una consulta usando el método extensor GroupBy (equivale al listado 7.36)

var res5 = facturas.

GroupBy(f => f.Cliente). SelectMany(g => g, (g, fv) => new { g, fv }). Where(f => f.fv.Articulos.Count > 3). OrderByDescending(c => c.g.Key.País). Select(c => new { Cli= c.g.Key, Facts = c.g });

In document c# linq (página 123-127)