DE .NET, SQLSERVER Y MÁS, APRENDE CONMIGO!✔

Desarrollo de todo tipo de aplicaciones y Administración de Base de datos con Tecnología Microsoft


UNETE

Sentencias Básicas

0

 

Sentencia Básicas

La forma más sencilla de realizar una consulta sobre LINQ es obtener una lista del elemento que queremos recuperar. Así, si quisiéramos obtener una lista de Clientes, la consulta adecuada para ello sería la siguiente:

1

2

var listaClientes = from c in DataLists.ListaClientes

                                     select c;

La cláusula from de LINQ es más fácil de entender si la interpretamos como si fuera un foreach. De hecho, el siguiente fragmentos de código realizaría más o menos la misma tarea: declarar una variable de tipo Cliente e iterar sobre la lista DataLists.ListaClientes:

1

2

3

4

5

6

7

8

// Declaramos una lista de clientes

IEnumerable<Cliente> listaClientes = new List<Cliente>();

 

// Declaramos una variable de rango "c" que iterará sobre DataLists.ListaClientes

foreach( Cliente c in DataLists.ListaClientes) // <<-- Equivaldrá a “from c in DataLists.ListaClientes”

{

    ((List<Cliente>)listaClientes).Add(c);     // <<-- Equivaldrá a “select c”;

}

Por lo tanto, la razón por la que el from va antes del select es poder declarar la variable de rango c y tener la posibilidad de hacer uso de ella en posteriores cláusulas, como select, orderby…


Obtener un campo concreto del objeto

Como acabamos de decir, la sentencia select permite hacer uso de la variable de rango definida en la sentencia from. Por lo tanto, ¿sería posible, en lugar de proyectar un objeto completo, hacer que nuestra lista se componga de los elementos contenidos en un campo específico en lugar de hacerlo con el objeto entero? Definitivamente, sí. Aquí está el ejemplo:

1

2

var listaNombresClientes = from c in DataLists.ListaClientes

                           select c.Nombre;

Si analizamos el resultado, veremos que no estamos recorriendo un listado de objetos Cliente, sino que la lista contiene simples cadenas de texto, correspondientes



Obtener varios campos del objeto: tipos anónimos

Quizás alguien se esté preguntando ahora: ¿y si quiero obtener más de un campo, digamos el nombre del cliente y su fecha de nacimiento? ¿Podría hacer algo como lo siguiente?:

1

2

var listaNombreFechaClientes = from c in DataLists.ListaClientes

                               select c.Nombre, c.FechaNac;

Desgraciadamente, no. LINQ se parece a SQL, pero no es SQL. Si queremos recuperar más de un elemento deberemos hacer uso bien de una clase o estructura que declaremos explícitamente para ello (por ejemplo, creando un nuevo Cliente), bien haciendo uso de un tipo anónimo.

El Framework 3.0 relativiza la necesidad de codificar un conjunto de constructores, cada cual con unos parámetros distintos, permitiendo crear un único constructor que aglutine las tareas básicas de inicialización y dejando al programador que añada manualmente las propiedades que estime oportunas a la hora de instanciar el objeto. Así, si quisiéramos devolver los campos «Nombre» y «FechaNac» de la clase «Cliente», podríamos hacer lo siguiente:

1

2

var listaClientesIncompletos = from c in DataLists.ListaClientes

                               select new Cliente { Nombre = c.Nombre, FechaNac = c.FechaNac };

Fabuloso, ¿verdad? Hemos creado un nuevo objeto Cliente y le hemos asignado valor a sus propiedades Nombre y FechaNac de forma dinámica sin necesidad de un constructor específico que espere esos parámetros. Podemos realizar esta operación con cualquier objeto que disponga de propiedades públicas.

Sin embargo, esto acarrea un problema. Echemos un vistazo al contenido de uno de los objetos pertenecientes a la lista devuelta por la sentencia LINQ:



Hemos instanciado un nuevo objeto de la clase Cliente, pero hacer esto implica que los tipos básicos se inicialicen con su valor por defecto. En este caso, la propiedad entera Id se inicializa con el valor 0, cuando obviamente sabemos que este valor no se corresponde con el Id real de ese campo en nuestra «base de datos». Quizás en nuestro código seamos perfectamente conscientes de este hecho, pero ¿lo sabrá la persona que, en un futuro, tenga que mantener este código?

Descartando utilizar un objeto completo para obtener tan sólo un puñado de campos, nos quedaría la opción de crear una clase específica para alojar estos dos campos. Sin embargo, esta operación puede darse demasiada frecuencia como para tener que crear una clase específica para todas las posibles combinaciones de campos de una lista que queramos recuperar. Esta tarea podría resultar demasiado tediosa. Por ello, el framework nos ofrece un artefacto que solucionará de un plumazo todos los problemas relacionados con esta pequeña disyuntiva: los tipos anónimos.

Un tipo anónimo no es más que un tipo generado de forma dinámica. Fin. No hay más misterio.

Al igual que hacíamos con el «no-constructor» de la clase Cliente pasándole el nombre de las propiedades junto a sus valores a la hora de crearlo, podemos hacer la misma operación sin necesidad de que la clase exista. Hablando en plata, nos inventamos la clase por el camino, y el compilador, a partir del punto en el que hayamos declarado el nuevo tipo, nos permitirá acceder a sus propiedades.

Veámoslo mejor con un ejemplo:

1

2

3

4

5

6

// Declaramos (y cumplimentamos) el tipo anónimo

var tipoAnonimo = new { PropiedadEntera = 1, PropiedadCadena = "cadena" };

 

// Mostramos por pantalla su contenido

Console.WriteLine(String.Format("El contenido del tipo anónimo es {0} y {1}",

    tipoAnonimo.PropiedadEntera, tipoAnonimo.PropiedadCadena));

La primera línea crearía un tipo anónimo (nótese que lo declaramos como var) con dos atributos (que se tipan en tiempo de compilación a int y string respectivamente, a partir de los tipos asignados):

·         AnonymousType tipoAnonimo

·         PropiedadEntera (int)

·         PropiedadCadena (string)

De nuevo nos enfrentaremos a la disyuntiva del uso de var en nuestro código: perderemos legibilidad a cambio de ganar versatilidad. Si se pidiera mi opinión, en este caso yo estaría dispuesto a realizar el sacrificio. Por lo tanto, si incluimos un tipo anónimo en la cláusula select de una sentencia LINQ, podríamos hacer lo siguiente:

1

2

var listaNombreFechaClientes = from c in DataLists.ListaClientes

                               select new { NombreCliente = c.Nombre, FechaNacimiento = c.FechaNac};

Si observamos el contenido del objeto con una inspección, veremos que el listado devuelto será una lista de Anonymous Type compuesto por los campos NombreCliente (string) y FechaNacimiento (DateTime), propiedades que nos acabamos de inventar pertenecientes, también, a un tipo que nos acabamos de inventar.



Si el lector es astuto, puede que piense en un nuevo problema potencial. Hasta ahora, todo muy bonito y muy potente, pero… si el tipo es anónimo y no podemos declarar una clase de su tipo, ¿cómo narices accedemos a sus elementos?

La respuesta puede sonar brusca: «a pelo». Que el tipo sea anónimo no implica que la variable que utilicemos para almacenar los datos no esté fuertemente tipada. Lo único que estamos haciendo es delegar en el compilador la tarea de asignar el tipo, pero una vez que el compilador conoce el tipo del objeto, nos permitirá usarlo sin provocar errores de compilación. Así, podríamos usar el siguiente bucle para acceder a los elementos de la lista devuelta por la sentencia LINQ:

1

2

3

foreach(var nombreFecha in listaNombreFechaClientes)

    Console.WriteLine(string.Format("El cliente {0} nació el {1}",

        nombreFecha.NombreCliente, nombreFecha.FechaNacimiento));

 

El resultado de esta llamada sería el siguiente:



De nuevo, el avispado lector probablemente se haya dado cuenta de otro potencial problema a la hora de utilizar tipos anónimos: ¿y si, en lugar de utilizar la variable que me ha devuelto la sentencia LINQ, quisiera pasar ese listado como parámetro a una función. ¿Cómo nos las arreglamos? Aquí el framework se rinde y pide el cambio, ya que un tipado fuerte es incompatible con esta funcionalidad. Por lo tanto, podemos decir que

1.       Sí, es posible pasar como parámetro un tipo anónimo a una función o método.

2.       A partir de este punto, el framework no se responsabiliza de los posibles daños ocasionados por tal osadía.

Lo que queremos decir es que mientras nos movamos dentro de un único ámbito o scope (por ejemplo, dentro de una única función), el framework será capaz de inferir el tipo de cada uno de los elementos anónimos que tengamos a bien generar. Sin embargo, en el momento en el que creemos una función y ésta reciba un parámetro de tipo anónimo, el framework se lavará las manos. El compilador, en un alarde de magnanimidad, nos permitirá que invoquemos a tantos métodos y propiedades del tipo anónimo, pero dejando a nuestra responsabilidad el hecho de que los elementos invocados existan y que sus parámetros y formatos sean correctos. Entramos en el terreno del tipado débil.

Por lo tanto, si quisiéramos generar una función que reciba una lista de elementos anónimos y realizara la misma operación que vimos más arriba, bastaría con lo siguiente:

1

2

3

4

5

6

private static void mostrarResultados(IEnumerable<dynamic> listado)

{

    foreach (var nombreFecha in listado)

        Console.WriteLine(string.Format("El cliente {0} nació el {1}",

            nombreFecha.NombreCliente, nombreFecha.FechaNacimiento));

}

Vemos que el parámetro es de tipo IEnumerable<dynamic>. En realidad bastaría con pasar un parámetro de tipo dynamic, que se correspondería con el tipo anónimo en sí, pero dado que sabemos que vamos a recibir una colección de tipos anónimos, seamos amables e indiquémosle a nuestro compilador esta información, ahorrándole unos cuantos ciclos de CPU a la máquina que realizará la compilación y algún que otro dolor de cabeza al colega que tendrá que encargarse en un futuro de mantener nuestra chapuza nuestro código.

Si desde la función original invocamos el método pasándole la consulta generada por LINQ en el ejemplo anterior, todo funcionará como la seda, ya que los nombres y tipos de las propiedades de nuestro tipo anónimo (NombreCliente (string) y FechaNacimiento (DateTime)) coinciden por las que espera nuestro método.

Pero… ¿y si no es así? ¿Y si en lugar de recibir un campo NombreCliente recibimos un campo llamado Nombre a secas? ¿Qué ocurriría? La respuesta es fácil de imaginar: Pum.



Por lo tanto, tal y como aconsejan las autoridades sanitarias con el alcohol, podemos decir lo mismo con los tipos anónimos: sí, pero consúmalos con responsabilidad. No permita que un tercero se vea perjudicado por no tomar las medidas oportunas.

Más adelante seguiremos con LINQ, mostrando cómo podemos aplicar filtros, ordenaciones 

 


Tal vez te interesen estas entradas

No hay comentarios