.NET LINQ desde cero
Spanish (Español) translation by Steven (you can also view the original English article)
Como desarrolladores de software, dedicamos mucho tiempo a extraer y mostrar datos de diferentes fuentes de datos. Ya sea un servicio web XML de algún tipo o una base de datos relacional con todas las funciones, nos hemos visto obligados a aprender diferentes métodos de acceso a datos. ¿No sería fantástico si el método de acceso fuera el mismo para todas las fuentes de datos? Bueno, estamos de suerte porque, a partir del lanzamiento de C# 3.0 y el Framework .NET 3.5, LINQ ha venido a cambiar el juego para siempre.
Detalles del tutorial
- Introducción a la sintaxis de LINQ
- Proyecciones usando LINQ
- Refinando datos
- Operadores estándar
Visión general del acceso a los datos actuales
En la plataforma .NET hemos estado y todavía estamos utilizando ADO.NET
para acceder a diferentes fuentes de datos. La comunidad de código abierto también ha proporcionado
los desarrolladores con una serie de alternativas.
Language Integrated Query es la nueva adición a la familia .NET
y como su nombre indica es el tipo de acceso a datos de estilo de consulta que
es totalmente compatible con el idioma para unificar de manera efectiva la forma en que accedemos a los datos
y para hacer nuestras vidas más fáciles. LINQ puede apuntar a una serie de fuentes diferentes, como Oracle,
MSSQL, XML y algunos otros, pero por ahora nos centraremos en lo más básico de
todos, LINQ a Objects.
LINQ a Objects.
Normalmente, para procesar y refinar los datos dentro de nuestras listas
y varias otras estructuras de datos, hemos utilizado el bucle 'foreach' u otro
tipo de método de bucle para iterar a través de los objetos y procesarlos uno por
uno de acuerdo a alguna condición. Esto está bien, pero, francamente, requiere una gran cantidad de
codificación básica que todos deseamos no tener que escribir. Esencialmente hemos tenido que decirle al
compilador todos los detalles del proceso para manipular los datos.
Aquí es exactamente donde LINQ brilla mejor. Lo que LINQ nos permite
hacer es simplemente decirle al compilador lo que nos gustaría realizar y dejar que el compilador funcione
de la mejor manera para lograrlo. Si has usado la sintaxis SQL anteriormente, las semejanzas masivas
entre LINQ y cualquier dialecto de SQL será lo primero que notarás.
Al igual que SQL, LINQ también soporta "select", "from", "where", "join", "group by"
y "order by". Aquí hay un ejemplo simple de consultar una lista de objetos:
Inicialización de la lista:
1 |
|
2 |
List<Car> ListOfCars = new List<Car>() |
3 |
{
|
4 |
new Car {name = "Toyota" , owner = "Alex" , model = 1992}, |
5 |
new Car {name = "Mitsubishi", owner = "Jeff" , model = 2005}, |
6 |
new Car {name = "Land Rover", owner = "Danny", model = 2001}, |
7 |
new Car {name = "BMW" , owner = "Danny", model = 2006}, |
8 |
new Car {name = "Subaru" , owner = "Smith", model = 2003} |
9 |
};
|
La consulta:
1 |
|
2 |
IEnumerable<Car> QueryResult = from car in ListOfCars |
3 |
select car; |
La primera parte del código anterior simplemente rellena una lista
Con cuatro instancias de la clase 'Car'. La siguiente parte del código, sin embargo, utiliza la
"from" y "select" para seleccionar un grupo de objetos. La principal diferencia
entre SQL y LINQ es que la palabra clave "from" aparece antes de la palabra clave "select"
porque primero debemos definir el objeto que queremos operar. Finalmente
la cláusula "select" le dice al compilador lo que deseamos extraer en esta consulta. El anterior
código simplemente extrae todo lo que está en la lista y lo asigna a la variable
"QueryResult".
Cuando consultamos cosas desde objetos (LINQ to Objects), nuestras
consultas siempre devuelven una lista de objetos "IEnumerable<T>". Esencialmente el
tipo "IEnumerable" es el tipo de lista que expone el enumerador, que
admite una iteración simple sobre una colección no genérica, y <T>
es el tipo de cada entrada en la lista.
No te preocupes si no estás familiarizado con los "enumeradores" y los "genéricos". Sólo
recuerda que el resultado de las consultas LINQ es siempre una colección como datos
estructurados que permiten iterar a través de ellos utilizando un bucle como se muestra
abajo:
1 |
|
2 |
foreach(Car car in QueryResult) |
3 |
Console.WriteLine(car.name); |
Aprendimos que LINQ siempre devuelve una estructura de colección similar
a cualquier otra lista. Sin embargo, la consulta LINQ no se ejecuta hasta que su resultado es
accedido por alguna otra pieza de código, como el bucle "foreach" arriba. Esto es para
permitirnos definir continuamente la consulta sin la sobrecarga re-evaluando
cada nuevo paso en la consulta.
Proyecciones
Hasta ahora tan bueno; pero la mayoría de las veces, nuestras consultas necesitarán
ser más complejas; así que vamos a tratar de proyectar datos. En SQL, Proyección significa seleccionar
el nombre de la(s) columna(s) de la(s) tabla(s) que uno desea ver aparecer en el resultado
de la consulta. En el caso de LINQ a Objects, se realizará una Proyección
en un tipo de resultado de consulta diferente al tipo de objeto que realizamos
en la consulta.
Hay dos tipos de proyecciones que podemos hacer. Podemos
realizar una proyección basada en un tipo de objeto existente o ir por completo
a la otra forma mediante el uso de tipos anónimos. El siguiente ejemplo es del primer
tipo:
1 |
|
2 |
IEnumerable<CarOwner> QueryResult = from car in ListOfCars |
3 |
select new CarOwner { owner_name = car.owner }; |
En el código anterior, el tipo de resultado de la consulta se declara como
<CarOwner>, que es diferente de <Car> , el tipo con el que se inicializa la variable 'ListOfCar'. Tenemos
también que usé la palabra clave "new" y he hecho algunas tareas dentro de la curva
de llaves. En el código anterior, el uso de "select" con la palabra clave "new" le indica al
compilador para crear una instancia de un nuevo objeto 'CarOwner' para cada entrada en el resultado de la consulta.
También asignando valores al nuevo tipo hemos inicializado cada instancia
de la clase 'CarOwner'.
No obstante, si aún no tienes un tipo definido para
utilizar, todavía puedes realizar proyecciones utilizando tipos anónimos.
Proyecciones usando tipos anónimos
Sería una gran molestia si, para cada Proyección, fueras
obligado a crear un nuevo tipo. Por eso, a partir de C# 3.0, soporte para tipos
anónimos fue agregado al lenguaje. Un tipo anónimo se declara usando la palabra clave "var".
Le dice al compilador que el tipo de la variable es desconocido hasta que
se asigna por primera vez.
1 |
|
2 |
var QueryResult = from car in ListOfCars |
3 |
select new { |
4 |
car_name = car.name, |
5 |
owner_name = car.owner |
6 |
};
|
7 |
|
8 |
foreach(var entry in QueryResult) |
9 |
Console.WriteLine(entry.car_name); |
Lo anterior es un ejemplo de realizar una consulta con Anonymous.
El único problema a tener en cuenta es que el compilador no
permite devolver tipos anónimos de los métodos.
Acceder a las propiedades de un tipo anónimo es fácil. En Visual Studio 2008, el Código
Completion/Intellisense también lista las propiedades expuestas por el tipo Anónimo.


Refinamiento de datos
Por lo general, como parte de la consulta LINQ, también necesitamos refinar el resultado de la
consulta especificando una condición. Al igual que SQL, LINQ también usa la cláusula "where"
para decirle al compilador qué condiciones son aceptables.
1 |
|
2 |
IEnumerable<Car> QueryResult = from car in ListOfCars |
3 |
where car.name == "Subaru" |
4 |
select car; |
El código anterior demuestra el uso de la cláusula "where" y
La condición a seguir. Para seguir definiendo múltiples condiciones, LINQ soporta
las construcciones 'and' (&& amp) y 'or' (||). La parte "where" de la consulta debe ser siempre una
expresión booleana, de lo contrario el compilador se quejará.
Order By
Al consultar objetos, es posible confiar en la consulta
obtenida por ya está ordenada. Si ese no es el caso, LINQ puede hacerse cargo de eso
mediante el uso de la cláusula "order by" que asegurará que el resultado de su consulta sea
ordenado apropiadamente.
1 |
|
2 |
IEnumerable<Car> QueryResult = from car in ListOfCars |
3 |
orderby car.model |
4 |
select car; |
Si ejecutas el código anterior, verás que el resultado de la
consulta está ordenada en orden ascendente. Puedes alterar el orden usando las palabras clave "ascending" y "descending",
y además cambia el orden especificando más de un campo para ordenar
por. El siguiente código muestra cómo:
1 |
|
2 |
IEnumerable<Car> QueryResult = from car in ListOfCars |
3 |
orderby car.model descending |
4 |
select car; |
Agrupar
LINQ también permite agrupar el resultado de la consulta por el valor de una
propiedad específica como se muestra en este ejemplo:
1 |
|
2 |
var QueryResult = from car in ListOfCars |
3 |
group car by car.owner into carOwnersGroup |
4 |
select carOwnersGroup.Key; |
Como puedes ver, LINQ admite la cláusula "group by" para
especificar qué objeto y por qué propiedad agrupar por. La palabra clave "into" será la que
luego nos permite proyectar sobre un resultado de agrupación al que se puede acceder mediante la propiedad
"Key".
Joins
LINQ admite la unión de datos de diferentes colecciones en un solo
resultado de la consulta. Puedes hacer esto usando la palabra clave "join" para especificar qué objetos
vas a unir; y usar la palabra clave "on" para especificar la relación coincidente entre
los dos objetos.
Inicializando lista relacionada:
1 |
|
2 |
List<Car> ListOfCars = new List<Car>() |
3 |
{
|
4 |
new Car {name = "Mitsubishi", owner = "Jeff" , model = 2005}, |
5 |
new Car {name = "Land Rover", owner = "Danny", model = 2001}, |
6 |
new Car {name = "Subaru" , owner = "Smith", model = 2003}, |
7 |
new Car {name = "Toyota" , owner = "Alex" , model = 1992}, |
8 |
new Car {name = "BMW" , owner = "Danny", model = 2006}, |
9 |
};
|
10 |
|
11 |
List<CarOwner> ListOfCarOwners = new List<CarOwner>() |
12 |
{
|
13 |
new CarOwner {owner_name = "Danny", age = 22}, |
14 |
new CarOwner {owner_name = "Jeff" , age = 35}, |
15 |
new CarOwner {owner_name = "Smith", age = 19}, |
16 |
new CarOwner {owner_name = "Alex" , age = 40} |
17 |
};
|
Query:
1 |
|
2 |
var QueryResult = from car in ListOfCars |
3 |
join carowner in ListOfCarOwners on car.owner equals carowner.owner_name |
4 |
select new {name = car.name, owner = car.owner, owner_age = carowner.age}; |
En el código anterior, utilizando un tipo anónimo, hemos unido
los dos objetos en un solo resultado de consulta.
Jerarquías de objetos usando uniones grupales
Hasta ahora, hemos aprendido cómo podemos usar LINQ para construir una lista
plana de resultados de consulta. Con LINQ, también es posible lograr una consulta jerárquica
en el resultado utilizando "GroupJoin". En palabras simples, podríamos asignar objetos a
propiedades de cada entrada con consulta LINQ.
1 |
|
2 |
List<Car> ListOfCars = new List<Car>() |
3 |
{
|
4 |
new Car {name = "Mitsubishi", owner = "Jeff" , model = 2005}, |
5 |
new Car {name = "Land Rover", owner = "Danny", model = 2001}, |
6 |
new Car {name = "Subaru" , owner = "Smith", model = 2003}, |
7 |
new Car {name = "Toyota" , owner = "Alex" , model = 1992}, |
8 |
new Car {name = "BMW" , owner = "Danny", model = 2006}, |
9 |
};
|
10 |
|
11 |
List<CarOwner> ListOfCarOwners = new List<CarOwner>() |
12 |
{
|
13 |
new CarOwner {owner_name = "Danny", age = 22}, |
14 |
new CarOwner {owner_name = "Jeff" , age = 35}, |
15 |
new CarOwner {owner_name = "Smith", age = 19}, |
16 |
new CarOwner {owner_name = "Alex" , age = 40} |
17 |
};
|
18 |
|
19 |
var QueryResult = from carowner in ListOfCarOwners |
20 |
join car in ListOfCars on carowner.owner_name equals car.owner into carsGroup |
21 |
select new {name = carowner.owner_name, cars = carsGroup}; |
22 |
|
23 |
foreach(var carOwner in QueryResult) |
24 |
foreach(var car in carOwner.cars) |
25 |
Console.WriteLine("Owner name: {0}, car name: {1}, car model: {2}", carOwner.name, car.name, car.model); |
En el ejemplo anterior, la cláusula "Join" va seguida de "into".
Esto difiere de la operación de unión anterior que vimos. Aquí, la cláusula "into"
se utiliza para agrupar los automóviles por el propietario (en carsGroup) y asignar la agrupación a la
propiedad "coches" del tipo anónimo.
Operadores de consultas estándar
Hasta ahora, todo lo que hemos visto ha sido respaldado por la sintaxis de C# 3.0.
Sin embargo, todavía hay una gran cantidad de operaciones que C# 3.0 no realiza soporte.
Los operadores de consulta estándar proporcionan capacidades de consulta que incluyen
filtrado, proyección, agregación, clasificación y más. Por lo tanto, estas operaciones son compatibles
como métodos de la biblioteca LINQ y se pueden ejecutar en el resultado de una consulta como se muestra en la
siguiente captura de pantalla:


Estos operadores se enumeran a continuación para tu referencia.
Operadores Agregados
- Sum: devuelve la suma de todas las entradas.
- Max: devuelve la entrada con el valor máximo.
- Min: devuelve la entrada con el valor mínimo.
- Average: devuelve el valor promedio de la colección.
- Aggregate: utilizado para crear una agregación personalizada
- LongCount: cuando se trata de una colección grande, este método devolverá un valor hasta el valor más grande admitido por la clase "long"
- Count: devuelve un "integer" para el conteo de elementos en la colección
Operadores de elementos
- First: devuelve la primera entrada de la colección de resultados.
- FirstOrDefault: si la colección está vacía, devolverá el valor predeterminado, de lo contrario devolverá la primera entrada de la colección.
- Single: devolverá solo el elemento de la colección.
- SingleOrDefault: si la colección está vacía, devolverá el valor predeterminado, de lo contrario devolverá solo el elemento de la colección
- Last: devuelve la última entrada de la colección.
- LastOrDefault: si la colección está vacía, devolverá el valor predeterminado, de lo contrario, devolverá la última entrada de la colección
- ElementAt: devuelve el elemento en la posición especificada
- ElementAtOrDefault: si está vacía, devolverá el valor predeterminado, de lo contrario, devolverá el elemento en la posición especificada
Establecer operadores relacionados
- Except: similar a la combinación izquierda en SQL, devolverá las entradas de un conjunto que no existe en otro conjunto
- Union: devuelve todas las entradas de ambos objetos
- Intersect: devuelve los mismos elementos de cualquiera de los conjuntos.
- Distinct: devuelve entradas únicas de la colección.
Operadores de Generación
- DefaultIfEmpty: si el resultado está vacío, devuelve el valor predeterminado
- Repeat: se repite en los objetos que se devuelven por el número especificado de veces
- Empty: devolverá una colección IEnumerable vacía
- Range: devuelve un rango de números para un número inicial y un conteo especificados
Operadores de refino
- Where: devolverá objetos que cumplan con la condición especificada
- OfType: devolverá objetos del tipo especificado
Operadores de conversión
- ToLookup: devuelve el resultado como una búsqueda
- ToList: devuelve el resultado como una colección de listas
- ToDictionary: devuelve el resultado como un diccionario
- ToArray: devuelve el resultado como una colección de Array
- AsQueryable: devuelve el resultado como un IQueryable<T>
- AsEnumerable: devuelve el resultado como un IEnumerable<T>
- OfType: filtra la colección según el tipo especificado
- Cast: se utiliza para convertir una colección de tipo débil en una colección de tipo fuerte
Operadores de particionamiento
- Take: devuelve un número especificado de registros
- Takewhile: devuelve un número especificado de registros mientras que la condición especificada se evalúa como verdadera
- Skip: omite el número especificado de entradas y devuelve el resto
- SkipWhile: omite el número especificado de entradas mientras que la condición especificada se evalúa como verdadera
Operadores cuantificadores
- Any: devuelve verdadero o falso para una condición específica
- Contains: devuelve verdadero o falso para la existencia del objeto especificado
- All: devuelve verdadero o falso a todos los objetos que cumplen la condición especificada
Operadores Join
- Join: devuelve entradas donde las claves en conjuntos son las mismas
- GroupJoin: se utiliza para crear objetos jerárquicos basados en una relación maestra y detallada
Operadores de Igualdad
- SequenceEqual: devuelve true cuando las colecciones son iguales
Operadores de ordenamiento
- Reverse: devuelve una colección invertida
- ThenBy: usado para realizar una clasificación adicional
- ThenByDescending: se utiliza para realizar una clasificación adicional en orden descendente
- OrderBy: usado para definir un orden
- OrderByDescending: se utiliza para definir el orden descendente
Operadores de Proyección
- SelectMany: se utiliza para aplanar una colección jerárquica
- Select: usado para identificar las propiedades a devolver.
Operadores de Concatenacion
- Concat: se utiliza para concatenar dos colecciones.
¿Y ahora qué?
LINQ ha demostrado ser muy útil para consultar objetos, y la sintaxis similar a SQL hace que sea fácil
de aprender y usar. Además, la gran cantidad de operadores estándar permite encadenar a varios operadores
para realizar consultas complejas. En un seguimiento de este tutorial, revisaremos cómo se puede usar LINQ para
consultar bases de datos y contenido XML...
Vende Scripts .NET y Componentes en CodeCanyon
- Síguenos en Twitter o suscríbete a Nettuts + RSS Feed para obtener los mejores tutoriales de desarrollo web en la web.




