Beruflich Dokumente
Kultur Dokumente
El proyecto LINQ
Mayo de 2006
En esta página
Utilizamos el término consultas integradas en los lenguajes para indicar que las consultas son una
característica integrada del lenguaje de programación principal del desarrollador (por ejemplo C#,
Visual Basic). Las consultas integradas en los lenguajes permiten que las expresiones de consulta
msdn.microsoft.com/…/bb308959.aspx 1/24
14/12/2010 El proyecto LINQ
se beneficien de los metadatos ricos, verificación de sintaxis en tiempo de compilación, tipado
estático y ayuda IntelliSense que antes estaban disponibles solo para el código imperativo. Las
consultas integradas en los lenguajes también hacen posible aplicar una única facilidad declarativa
de propósito general a toda la información en memoria, y no solo a la información proveniente de
fuentes externas.
Las consultas integradas en los lenguajes .NET definen un conjunto de operadores de consulta
estándar de propósito general que hacen posible que las operaciones de recorrido, filtro y
proyección sean expresadas de una manera directa pero declarativa en cualquier lenguaje de
programación. Los operadores de consulta estándar permiten aplicar las consultas a cualquier
fuente de información basada en IEnumerable<T>. LINQ permite que terceros fabricantes
aumenten el conjunto de operadores de consulta estándar, añadiendo los operadores de dominio
específico que sean apropiados para el dominio o la tecnología de destino. Más importante aún es
que terceros fabricantes también pueden reemplazar los operadores de consulta estándar con sus
propias implementaciones que ofrezcan servicios adicionales como la evaluación remota,
traducción de consultas, optimización, etc. Al adherirse a los convenios del patrón LINQ, tales
implementaciones gozarán de la misma integración en los lenguajes y soporte de herramientas que
los operadores de consulta estándar.
Principio de la página
Para ver las consultas integradas en los lenguajes en acción, comenzaremos con un sencillo
programa en C# 3.0 que utiliza los operadores de consulta estándar para procesar el contenido de
un array:
using System;
using System.Query;
using System.Collections.Generic;
class app {
static void Main() {
string[] names = { "Burke", "Connor", "Frank",
"Everett", "Albert", "George",
"Harris", "David" };
msdn.microsoft.com/…/bb308959.aspx 2/24
14/12/2010 El proyecto LINQ
Para comprender cómo funcionan las consultas integradas en los lenguajes, debemos analizar la
primera sentencia de nuestro programa.
IEnumerable<string> expr = from s in names
where s.Length == 5
orderby s
select s.ToUpper();
La variable local expr es inicializada con una expresión de consulta. Una expresión de consulta
opera sobre una o más fuentes de información aplicando uno o más operadores de consulta, ya
sean operadores de consulta estándar u operadores específicos de un dominio. Esta expresión
utiliza tres de los operadores de consulta estándar: Where, OrderBy y Select.
Visual Basic 9.0 soporta igualmente LINQ. He aquí la sentencia anterior escrita en Visual Basic 9.0:
Dim expr As IEnumerable(Of String) = From s in names _
Where s.Length = 5 _
Order By s _
Select s.ToUpper()
Tanto la sentencia de C# como la de Visual Basic mostradas aquí utilizan la sintaxis de consultas.
Del mismo modo que la sentencia foreach, la sintaxis de consultas es una notación declarativa
conveniente más concisa que el código que se podría escribir manualmente. Las sentencias
anteriores son semánticamente idénticas a la siguiente sintaxis explícita mostrada en C#:
IEnumerable<string> expr = names
.Where(s => s.Length == 5)
.OrderBy(s => s)
.Select(s => s.ToUpper());
Los argumentos de los operadores Where, OrderBy y Select se conocen como expresiones
lambda, que son fragmentos de código muy similares a los delegados. Ellas permiten que los
operadores de consulta estándar se definan individualmente como métodos y se conecten
utilizando notación de punto. Conjuntamente, estos métodos conforman las bases de un lenguaje
de consultas extensible.
Principio de la página
LINQ está construido enteramente sobre características de los lenguajes de propósito general,
algunas de las cuales son nuevas en C# 3.0 y Visual Basic 9.0. Cada una de estas características
tiene su utilidad propia, pero conjuntamente, estas características ofrecen una manera extensible
de definir consultas y API de consultas. En esta sección exploraremos estas características de los
lenguajes y cómo contribuyen a un estilo de consultas mucho más directo y declarativo.
Principio de la página
Muchos operadores de consulta permiten al usuario suministrar una función que realice un filtrado,
proyección o extracción de clave. Las facilidades de consulta se apoyan en el concepto de las
expresiones lambda, que ofrecen a los desarrolladores una manera conveniente de escribir
funciones que pueden ser pasadas como argumentos para su evaluación subsiguiente. Las
expresiones lambda son similares a los delegados del CLR y deben adherirse a una firma de método
definida por un tipo delegado. Para ilustrar esto, podemos expandir la sentencia anterior a otra
forma equivalente pero más explícita usando el tipo delegado Func:
Func<string, bool> filter = s => s.Length == 5;
Func<string, string> extract = s => s;
Func<string, string> project = s => s.ToUpper();
msdn.microsoft.com/…/bb308959.aspx 3/24
14/12/2010 El proyecto LINQ
Las expresiones lambda son la evolución natural de los métodos anónimos de C# 2.0. Por ejemplo,
podríamos haber escrito el ejemplo usando métodos anónimos de la siguiente forma:
Func<string, bool> filter = delegate (string s) {
return s.Length == 5;
};
A diferencia de los delegados, que son efectivamente código opaco, podemos interactuar con el
árbol de expresión como lo hacemos con cualquier otra estructura de nuestro programa. Por
ejemplo, este programa:
Expresión<Func<int, bool>> filter = n => n < 5;
msdn.microsoft.com/…/bb308959.aspx 4/24
14/12/2010 El proyecto LINQ
ParameterExpression left = (ParameterExpression)body.Left;
ConstantExpression right = (ConstantExpression)body.Right;
Esta capacidad de tratar las expresiones como datos en tiempo de ejecución es crítica para hacer
posible un ecosistema de librerías de terceros que aprovechen las abstracciones de consulta
básicas que son parte de la plataforma. La implementación del acceso a datos de DLinq aprovecha
esta capacidad para traducir los árboles de expresiones a sentencias T-SQL que pueden ser
evaluadas en la base de datos.
Principio de la página
Métodos extensores
Las expresiones lambda son una parte importante de la arquitectura de consultas. Los métodos
extensores son otra. Los métodos extensores combinan la flexibilidad del “tipado de pato” que han
hecho populares los lenguajes dinámicos con el rendimiento y la validación en tiempo de
compilación de los lenguajes de tipado estático. Mediante los métodos extensores, terceros
fabricantes pueden aumentar el contrato público de un tipo con nuevos métodos, permitiendo a la
vez a autores de tipos individuales ofrecer su propia implementación especializada de esos
métodos.
Los métodos extensores se definen como métodos estáticos en clases estáticas, pero se marcan
con el atributo [System.Runtime.CompilerServices.Extension] en los metadatos del CLR. Se
propone que los lenguajes ofrezcan una sintaxis directa para los métodos extensores. En C#, los
métodos extensores se indican mediante el modificador this aplicado al primer parámetro del
método extensor. Veamos la definición del operador de consulta más sencillo, Where:
namespace System.Query {
using System;
using System.Collections.Generic;
El tipo del primer parámetro de un método extensor indica a qué tipo de la extensión se aplica. En
el ejemplo anterior, el método extensor Where extiende el tipo IEnumerable<T>. Debido a que
Where es un método estático, podemos llamarlo directamente como a cualquier otro método
estático:
IEnumerable<string> expr = Sequence.Where(names,
s => s.Length < 6);
Sin embargo, lo que hace únicos a los métodos extensores es que también pueden ser llamados
utilizando sintaxis de instancia:
IEnumerable<string> expr = names.Where(s => s.Length < 6);
msdn.microsoft.com/…/bb308959.aspx 5/24
14/12/2010 El proyecto LINQ
Los métodos extensores se resuelven en tiempo de compilación sobre la base de qué métodos
extensores están en ámbito. Cuando un espacio de nombres en importado mediante la sentencia
using de C# o la sentencia Import de VB, todos los métodos extensores definidos por las clases
estáticas de ese espacio de nombres son traídas al ámbito actual.
Los usuarios que quieran sustituir los operadores de consulta estándar para un tipo específico
pueden (a) definir sus propios métodos con los mismos nombres y firmas compatibles en el tipo
específico o (b) definir nuevos métodos extensores con los mismos nombres que extiendan el tipo
específico. Los usuarios que quieran esquivar totalmente los operadores de consulta estándar
pueden simplemente no poner en ámbito System.Query y escribir sus propios métodos extensores
para IEnumerable<T>.
Los métodos extensores reciben la prioridad más baja en términos de resolución, y son utilizados
únicamente cuando no se encuentra una coincidencia en el tipo de destino y sus tipos base. Esto
permite que los tipos definidos por el usuario ofrezcan sus propios operadores de consulta que
tengan precedencia sobre los operadores estándar. Por ejemplo, observe la siguiente colección
personalizada:
public class MySequence : IEnumerable<int> {
public IEnumerator<int> GetEnumerator() {
for (int i = 1; i <= 10; i++)
yield return i;
}
IEnumerator IEnumerable.GetEnumerator() {
return GetEnumerator();
}
El operador OfType es uno de los pocos operadores de consulta estándar que no extienden una
fuente de información basada en IEnumerable<T>. Echemos un vistazo al operador de consulta
OfType:
public static IEnumerable<T> OfType<T>(this IEnumerable source) {
foreach (object item in source)
if (item is T)
yield return (T)item;
}
msdn.microsoft.com/…/bb308959.aspx 6/24
14/12/2010 El proyecto LINQ
OfType acepta no solamente fuentes basadas en IEnumerable<T>, sino también fuentes
programadas contra la versión no parametrizada de la interfaz IEnumerable que estaba disponible
en la versión 1 de .NET Framework. El operador OfType permite a los usuarios aplicar los
operadores de consulta estándar a colecciones clásicas de .NET como la siguiente:
// "clásica" – no puede utilizarse directamente con los
// operadores de consulta
IEnumerable classic = new OlderCollectionType();
En este ejemplo, la variable modern produce la misma secuencia de valores que classic, pero su
tipo es compatible con el código moderno basado en IEnumerable<T>, incluyendo los operadores
de consulta estándar.
El operador OfType es también útil para nuevas fuentes de información, ya que permite el filtrado
de los valores provenientes de una fuente en base al tipo. Al producir la nueva secuencia, OfType
simplemente omite los miembros de la secuencia original que no son compatibles con el argumento
de tipo. Analice el sencillo programa que extrae las cadenas de un array heterogéneo:
object[] vals = { 1, "Hello", true, "World", 9.1 };
IEnumerable<string> justStrings = vals.OfType<string>();
Principio de la página
Los lectores observadores pueden haber notado que el operador estándar Where se implementa
utilizando la construcción yield introducida en C# 2.0. Esta técnica de implementación es común
para todos los operadores estándar que devuelven secuencias de valores. El uso de yield ofrece
un beneficio interesante, que consiste en que la consulta no es realmente evaluada hasta que se
itera sobre ella, ya sea mediante una sentencia foreach o manualmente, utilizando los métodos
GetEnumerator y MoveNext subyacentes. Esta evaluación diferida permite que las consultas se
mantengan como valores basados IEnumerable<T> que pueden ser evaluados múltiples veces,
cada vez produciendo resultados potencialmente diferentes.
Para ver cómo funciona la evaluación diferida, analice el siguiente programa, que ejecuta una
consulta sencilla sobre un array:
// se declara una variable que contiene varias cadenas
string[] names = { "Allen", "Arthur", "Bennett" };
// se evalúa la consulta
foreach (string item in ayes)
Console.WriteLine(item);
msdn.microsoft.com/…/bb308959.aspx 7/24
14/12/2010 El proyecto LINQ
La consulta es evaluada cada vez que se itera sobre la variable ayes. Para indicar que se requiere
una copia cacheada de los resultados, debemos simplemente añadir un operador ToList o ToArray
a la consulta de la siguiente forma:
// se declara una variable que contiene varias cadenas
string[] names = { "Allen", "Arthur", "Bennett" };
Tanto ToArray como ToList fuerzan la evaluación inmediata de la consulta. Eso también es válido
para los operadores de consulta estándar que devuelven valores únicos (por ejemplo First,
ElementAt, Sum, Average, All, Any).
Principio de la página
La interfaz IQueryable-T-
El mismo modelo de ejecución diferida es generalmente deseado para las fuentes de datos que
implementan la funcionalidad de consultas mediante árboles de expresiones, como DLinq. Estas
fuentes de datos pueden beneficiarse de implementar la interfaz IQueryable<T>, para la cual
todos los operadores de consulta requeridos por el patrón LINQ se implementan utilizando árboles
de expresiones. Cada IQueryable<T> tiene una representación de “el código necesario para
ejecutar la consulta” en la forma de un árbol de expresión. Todos los operadores de consulta
diferidos devuelven un nuevo IQueryable que aumenta ese árbol de expresión con una
representación de una llamada a ese operador de consulta. Por esta razón, cuando llega el
momento de evaluar la consulta, generalmente porque el IQueryable es enumerado, la fuente de
datos puede procesar el árbol de expresión que representa a la consulta entera en un solo “lote”.
Por ejemplo, una consulta DLinq compleja obtenida mediante numerosas llamadas a operadores de
consulta puede resultar en que una única consulta SQL sea enviada a la base de datos.
La ventaja para los implementadores de fuentes de datos de reutilizar esta funcionalidad diferida
implementando la interfaz IQueryable<T> es obvia. Para los clientes que escriben las consultas,
por la otra parte, es una gran ventaja tener un tipo común para las fuentes de información
remotas. Esto no solo les permite escribir consultas polimórficas que pueden ser utilizadas contra
diferentes fuentes de datos, sino que también abre la posibilidad de escribir consultas que operen
entre diferentes dominios.
Principio de la página
Las expresiones lambda y los métodos extensores nos ofrecen todo lo que necesitamos para las
consultas que simplemente filtran los miembros de una secuencia de valores. La mayoría de las
msdn.microsoft.com/…/bb308959.aspx 8/24
14/12/2010 El proyecto LINQ
expresiones de consulta realizan además una proyección de esos miembros, transformando
efectivamente los miembros de la secuencia original en miembros cuyo valor y tipo puede se
diferente del original. Para dar soporte a tales transformaciones, LINQ se apoya en una nueva
construcción conocida como expresiones de inicialización de objetos para crear nuevas instancias
de tipos estructurados. Para el resto de este documento, asumiremos que se ha definido el
siguiente tipo:
public class Person {
string name;
int age;
bool canCode;
Las expresiones de inicialización de objetos nos permiten construir fácilmente valores basados en
los campos y propiedades públicas de un tipo. Por ejemplo, para crear un nuevo valor de tipo
Person podemos escribir esta sentencia:
Person value = new Person {
Name = "Chris Smith", Age = 31, CanCode = false
};
Las expresiones de inicialización de objetos son una característica importante para las consultas
integradas en los lenguajes, ya que permiten la construcción de nuevos valores estructurados en
contextos en los que solo se permiten expresiones (como dentro de expresiones lambda y árboles
de expresiones). Por ejemplo, observe esta expresión de consulta que crea un nuevo objeto de
tipo Person para cada valor de la secuencia de entrada:
IEnumerable<Person> expr = names.Select(s => new Person {
Name = s, Age = 21, CanCode = s.Length == 5
});
Principio de la página
msdn.microsoft.com/…/bb308959.aspx 9/24
14/12/2010 El proyecto LINQ
Valores y tipos estructurados
El Proyecto LINQ soporta un estilo de programación centrado en datos en el que algunos tipos
existen principalmente para suministrar una “forma” estática de un valor estructurado en vez de
un objeto completo con su estado y comportamiento. Llevando esta premisa a su conclusión
lógica, frecuentemente ocurre que al desarrollador le interesa solo la estructura del valor, y la
necesidad de un tipo nombrado para esa estructura es de muy poca utilidad. Esto lleva a la
introducción de los tipos anónimos, que permiten definir nuevas estructuras “en línea” con su
inicialización.
En C#, la sintaxis para los tipos anónimos es similar a la sintaxis de inicialización de objetos, con la
excepción de que el nombre del tipo es omitido. Por ejemplo, observe las dos siguientes
sentencias:
object v1 = new Person {
Name = "Chris Smith", Age = 31, CanCode = false
};
Las variables v1 y v2 apuntan ambas a un objeto en memoria cuyo tipo del CLR tiene tres
propiedades públicas Name, Age y CanCode. Las variables se diferencian en que v2 hace
referencia a una instancia de un tipo anónimo. En términos del CLR, los tipos anónimos no son
diferentes de cualquier otro tipo. Lo que hace especiales a los tipos anónimos es que no tienen un
nombre significativo en el lenguaje de programación – la única manera de crear instancias de un
tipo anónimo es utilizando la sintaxis mostrada anteriormente.
Para permitir que las variables hagan referencia a instancias de tipos anónimos y al mismo tiempo
se beneficien del tipado estático, C# introduce la palabra clave var, que puede ser utilizada en
lugar del nombre del tipo en las declaraciones de variables locales. Por ejemplo, observe el
siguiente programa válido de C# 3.0:
var s = "Bob";
var n = 32;
var b = true;
La palabra clave var indica al compilador que deduzca (infiera) el tipo de la variable a partir del
tipo estático de la expresión utilizada para inicializar la variable. En el ejemplo, los tipos de s, n y
b son string, int y bool, respectivamente. Este programa es idéntico al siguiente:
string s = "Bob";
int n = 32;
bool b = true;
La palabra clave var es solo un mecanismo de conveniencia en el caso de variables cuyos tipos
tienen nombres significativos, pero una necesidad en el caso de variables que hacen referencia a
instancias de tipos anónimos.
var value = new {
Name = "Chris Smith", Age = 31, CanCode = false
};
msdn.microsoft.com/…/bb308959.aspx 10/24
14/12/2010 El proyecto LINQ
public string Name {
get { return _Name; } set { _Name = value; }
}
Los tipos anónimos no pueden ser compartidos a través de las fronteras de ensamblados; sin
embargo, el compilador garantiza que existirá a lo sumo un tipo anónimo para cada secuencia
diferente de pares nombre/tipo de propiedad dentro de cada ensamblado.
Debido a que los tipos anónimos se utilizan frecuentemente en proyecciones para seleccionar uno
o más miembros de un valor estructurado existente, podemos simplemente hacer referencia a los
campos o propiedades de otro valor en la inicialización de un tipo anónimo. Esto resulta en que al
nuevo tipo se le asocia una propiedad cuyo nombre, tipo y valor son copiados del campo o
propiedad referenciada.
Por ejemplo, observe este ejemplo, que crea un nuevo valor estructurado combinando las
propiedades de otros valores:
var bob = new Person { Name = "Bob", Age = 51, CanCode = true };
var jane = new { Age = 29, FirstName = "Jane" };
int ha = couple.Husband.Age; // ha == 51
string wn = couple.Wife.Name; // wn == "Jane"
Las referencias a campos o propiedades mostradas anteriormente son simplemente una sintaxis
conveniente para escribir la siguiente forma más explícita:
var couple = new {
Husband = new { Name = bob.Name, Age = bob.Age },
Wife = new { Name = jane.FirstName, Age = jane.Age }
};
En ambos casos, la variable couple obtiene su propia copia de las propiedades Name y Age
obtenidas de bob y jane.Los tipos anónimos se utilizan con mayor frecuencia en la cláusula select
de una consulta. Por ejemplo, observe la siguiente consulta:
var expr = people.Select(p => new {
p.Name, BadCoder = p.Age == 11
});
En este ejemplo, hemos sido capaces de crear una nueva proyección sobre el tipo Person que
msdn.microsoft.com/…/bb308959.aspx 11/24
14/12/2010 El proyecto LINQ
coincide con la forma que necesitamos para nuestro tratamiento, manteniendo las ventajas del
tipado estático.
Principio de la página
Principio de la página
Ordenación y agrupación
// ordenación de identidad
var s1 = names.OrderBy(s => s);
var s2 = names.OrderByDescending(s => s);
Las dos primeras expresiones de consulta producen nuevas secuencias basadas en la ordenación
de los miembros de la fuente en base a la comparación de cadenas. Las segundas dos consultas
producen nuevas secuencias basadas en la ordenación de los miembros de la fuente según la
longitud de cada cadena.
Para permitir múltiples criterios de ordenación, tanto OrderBy como OrderByDescending devuelven
SortedSequence<T> en vez de la interfaz genérica IEnumerable<T>. Solo dos operadores están
definidos sobre el tipo SortedSequence<T>: ThenBy y ThenByDescending, que aplican criterios de
ordenación adicionales (subordinados). Los propios ThenBy/ThenByDescending devuelven
SortedSequence<T>, permitiendo la utilización de cualquier cantidad de operadores
ThenBy/ThenByDescending:
string[] names = { "Burke", "Connor", "Frank", "Everett",
"Albert", "George", "Harris", "David" };
msdn.microsoft.com/…/bb308959.aspx 12/24
14/12/2010 El proyecto LINQ
"Everett"
Además de la familia de operadores OrderBy, entre los operadores de consulta estándar también
se incluye el operador Reverse. Reverse simplemente enumera una secuencia y produce los
mismos valores en orden inverso. A diferencia de OrderBy, Reverse no tiene en cuenta los propios
valores para determinar el orden, sino que se apoya únicamente en el orden en que los valores
son producidos por la fuente subyacente.El operador OrderBy impone una ordenación sobre una
secuencia de valores. Entre los operadores de consulta estándar también se incluye el operador
GroupBy, que impone la partición en grupos de los valores de una secuencia en base a una
función de extracción de clave. El operador GroupBy devuelve una secuencia de valores
IGrouping, uno para cada valor de clave distinto encontrado. Un IGrouping es un IEnumerable que
adicionalmente contiene la clave que fue utilizada para extraer su contenido:
public interface IGrouping<K, T> : IEnumerable<T> {
public K Key { get; }
}
Del mismo modo que Select, GroupBy permite suministrar una función de proyección que será
utilizada para poblar los miembros del grupo.
string[] names = { "Albert", "Burke", "Connor", "David",
"Everett", "Frank", "George", "Harris"};
Observe en este ejemplo que el tipo proyectado no tiene que ser el mismo tipo original. En este
caso, hemos creado una agrupación de enteros a caracteres a partir de una secuencia de
cadenas.
Principio de la página
Varios operadores de consulta estándar se han definido para permitir la acumulación de una
secuencia de valores en un único valor. El operador de acumulación más general es Aggregate,
definido de la siguiente forma:
public static U Aggregate<T, U>(this IEnumerable<T> source,
U seed, Func<U, T, U> func) {
U result = seed;
return result;
}
Por ejemplo, el siguiente programa utiliza Aggregate para acumular la cantidad general de
caracteres en un array de cadenas:
string[] names = { "Albert", "Burke", "Connor", "David",
"Everett", "Frank", "George", "Harris"};
Además del operador de propósito general Aggregate, entre los operadores de consulta estándar
también se incluyen un operador de propósito general Count y cuatro operadores de acumulación
(Min, Max, Sum y Average) que simplifican esas operaciones comunes de acumulación. Las
funciones de acumulación numéricas operan sobre una secuencia de tipos numéricos (por ejemplo,
int, double, decimal) o sobre secuencias de valores arbitrarios, siempre que se suministre una
función que proyecte los miembros de la secuencia a un tipo numérico.
El siguiente programa ilustra las dos formas del operador Sum antes descritas:
int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
string[] names = { "Albert", "Burke", "Connor", "David",
"Everett", "Frank", "George", "Harris"};
msdn.microsoft.com/…/bb308959.aspx 14/24
14/12/2010 El proyecto LINQ
int total2 = names.Sum(s => s.Length); // total2 == 46
Observe que la segunda sentencia Sum es equivalente al ejemplo anterior que utilizaba Aggregate.
Principio de la página
Operadores de acumulación
El operador Select exige que la función de transformación produzca un valor para cada valor de la
secuencia original. Si su función de transformación devuelve un valor que es en sí mismo una
secuencia, es responsabilidad del consumidor recorrer manualmente las sub-secuencias. Por
ejemplo, observe el siguiente programa, que descompone cadenas en palabras utilizando el
método String.Split:
string[] text = { "Albert was here",
"Burke slept late",
"Connor is happy" };
La utilización de SelectMany provoca que cada secuencia intermedia sea expandida como parte
de la evaluación normal.
Principio de la página
msdn.microsoft.com/…/bb308959.aspx 15/24
14/12/2010 El proyecto LINQ
Operadores de encuentro
En un programa orientado a objetos, los objetos relacionados con otros están generalmente
enlazados mediante referencias a objetos fáciles de navegar. Esto generalmente no se cumple
para fuentes de información externas, donde los registros de datos frecuentemente no tienen otra
opción que “apuntar” a otros de manera simbólica, mediante claves externas u otros datos que
permitan identificar unívocamente a la entidad apuntada. El concepto de encuentros se refiere a
la operación de combinar los elementos de una secuencia con los elementos con los que ellos
“coinciden” de otra secuencia.
El ejemplo anterior que utiliza SelectMany hace exactamente eso, buscar coincidencias de
cadenas con personas cuyos nombres son esas cadenas. Sin embargo, para este propósito
específico, el enfoque basado en SelectMany no es muy eficiente – recorrerá todos los elementos
de people para todos y cada uno de los elementos de names.
Utilizando toda la información de este escenario – las dos fuentes de información y las “claves”
por las que se deben combinar – en una única llamada a método, el operador Join es capaz de
hacer un mucho mejor trabajo:
string[] names = { "Burke", "Connor", "Frank", "Everett",
"Albert", "George", "Harris", "David" };
Esto puede parecer complicado, pero nos permitirá ver cómo las piezas encajan: el método Join es
aplicado a la fuente de datos “externa”, names. El primer argumento es la fuente de datos
“interna”, people. El segundo y tercer argumentos son expresiones lambda para extraer claves de
los elementos de las secuencias externa e interna, respectivamente. Estas claves son las que el
método Join utiliza para buscar las coincidencias de elementos. Aquí queremos que los propios
nombres coincidan con la propiedad Name de las personas. La expresión lambda final es entonces
responsable de producir los elementos de la secuencia resultante: es llamada para cada pareja de
elementos coincidentes n y p, y es utilizada para dar forma al resultado. En este caso, hemos
elegido descartar n y devolver p. El resultado final es la lista de elementos Person de people cuyo
Name está en la lista names.
Un pariente más potente de Join es el operador GroupJoin. GroupJoin se diferencia de Join en el
modo en que se utiliza la expresión lambda que da forma al resultado: en vez de ser invocada para
cada pareja individual de elementos externo e interno, será llamada solamente una vez para cada
elemento externo, con una secuencia de todos los elementos internos que coinciden con ese
elemento externo. Poniendo un ejemplo concreto:
string[] names = { "Burke", "Connor", "Frank", "Everett",
"Albert", "George", "Harris", "David" };
Esta llamada produce una secuencia de los nombres iniciales emparejados con la cantidad de
personas que tiene ese nombre. Por lo tanto, el operador GroupJoin permite basar los resultados
en el “conjunto de coincidencias” entero para un elemento externo.
Principio de la página
Sintaxis de consultas
La sentencia de C# foreach ofrece una sintaxis declarativa para la iteración sobre los métodos de
las interfaces IEnumerable/IEnumerator de .NET Framework. La sentencia foreach es
estrictamente opcional, pero es un mecanismo del lenguaje que ha demostrado ser muy
conveniente y popular.
msdn.microsoft.com/…/bb308959.aspx 16/24
14/12/2010 El proyecto LINQ
Apoyándose en este precedente, la sintaxis de consultas simplifica las expresiones de consulta
con una sintaxis declarativa para los operadores de consulta más comunes: Where, Join,
GroupJoin, Select, SelectMany, GroupBy, OrderBy, ThenBy, OrderByDescending y
ThenByDescending.
De modo similar a la sentencia foreach de C#, las expresiones de la sintaxis de consultas son más
compactas y fáciles de leer, pero completamente opcionales. Toda expresión que puede ser
escrita mediante sintaxis de consultas tiene una sintaxis correspondiente (más verbosa) utilizando
la notación de punto.
cuerpo-consulta ::=
cláusula-join*
(cláusula-from cláusula-join* | cláusula-let |
cláusula-where)*
cláusula-orderby?
(cláusula-select | cláusula-groupby)
continuación-consulta?
cláusula-join ::=
join nombreElemento in exprFuente
on exprClave equals exprClave
(into nombreElemento)?
msdn.microsoft.com/…/bb308959.aspx 17/24
14/12/2010 El proyecto LINQ
Por ejemplo, observe las dos siguientes expresiones de consulta:
var query1 = from p in people
where p.Age > 20
orderby p.Age descending, p.Name
select new {
p.Name, Senior = p.Age > 30, p.CanCode
};
El compilador trata estas expresiones de consulta como si hubieran sido escritas utilizando la
siguiente notación de punto explícita:
var query1 = people.Where(p => p.Age > 20) .OrderByDescending(p => p.Age) .ThenBy(p =>
p.Name) .Select(p => new { p.Name, Senior = p.Age > 30, p.CanCode });
var query2 = people.Where(p => p.Age > 20)
.OrderByDescending(p => p.Age)
.ThenBy(p => p.Name)
.GroupBy(p => p.CanCode,
p => new {
p.Name,
Senior = p.Age > 30,
p.CanCode
});
Las expresiones de consulta realizan una traducción mecánica en llamadas a métodos con
nombres específicos. La implementación exacta de los operadores de consulta que es elegida, por
lo tanto, depende tanto del tipo de las variables consultadas como de los métodos extensores que
están en ámbito.
Las expresiones de consulta mostradas hasta ahora han utilizado un único generador. Cuando se
utiliza más de un generador, cada generador subsiguiente es evaluado en el contexto de su
predecesor. Por ejemplo, observe esta ligera modificación de nuestra consulta:
var query = from s1 in names where s1.Length == 5 from s2 in names where s1 == s2 select s1 +
" " + s2;
Observe que la utilización de SelectMany hace que la expresión de consulta interna sea aplanada
msdn.microsoft.com/…/bb308959.aspx 18/24
14/12/2010 El proyecto LINQ
en el resultado externo.
Una clase especial de generador es la cláusula join, que introduce los elementos provenientes de
otra fuente que coinciden según las claves indicadas con los elementos de la cláusula from o join
precedente. Una cláusula join puede producir los elementos coincidentes uno a uno, pero si es
especificada con una cláusula into, los elementos coincidentes se devolverán como un grupo:
var query = from n in names
join p in people on n equals p.Name into matching
select new { Name = n, Count = matching.Count() };
No es nada sorprendente que esta consulta se expanda de una manera bastante directa en una
que ya hemos visto antes:
var query = names.GroupJoin(people, n => n, p => p.Name,
(n, matching) =>
new { Name = n, Count = matching.Count() }
);
Frecuentemente es útil tratar los resultados de una consulta como generador para una consulta
subsiguiente. Para dar soporte a esto, las expresiones de consulta utilizan la palabra clave into
para desplegar una nueva expresión de consulta después de una cláusula select o group.
La palabra clave into es especialmente útil para el post-procesamiento de los resultados de una
cláusula group by. Por ejemplo, observe este programa:
var query = from item in names
orderby item
group item by item.Length into lengthGroups
orderby lengthGroups.Key descending
select lengthGroups;
Esta sección ha descrito cómo C# implementa las expresiones de consulta. Otros lenguajes
pueden elegir ofrecer sintaxis explícita para operadores de consulta adicionales, o simplemente no
ofrecer las expresiones de consulta.
Es importante señalar que la sintaxis de consultas no está “cableada” de ninguna manera a los
operadores de consulta estándar. Es una característica puramente sintáctica que puede aplicarse
a cualquier cosa que satisfaga el patrón LINQ implementando los métodos subyacentes con los
nombres y firmas apropiados. Los operadores de consulta estándar descritos anteriormente hacen
esto utilizando métodos extensores para aumentar la interfaz IEnumerable<T>. Los desarrolladores
pueden utilizar la sintaxis de consultas sobre cualquier tipo que deseen, siempre que se aseguren
de que éste se adhiere al patrón LINQ, ya sea mediante implementación directa de los métodos
msdn.microsoft.com/…/bb308959.aspx 19/24
14/12/2010 El proyecto LINQ
necesarios o añadiéndolos como métodos extensores.
Esta extensibilidad es explotada en el propio Proyecto LINQ a través de la provisión de dos API
basadas en LINQ: DLinq, que implementa el patrón LINQ para el acceso a datos basados en SQL,
y XLinq, que permite las consultas LINQ sobre datos XML. Ambas extensiones se describen en las
siguientes secciones.
Principio de la página
Las consultas integradas en los lenguajes .NET pueden ser utilizadas para consultar almacenes de
datos relacionales sin abandonar la sintaxis o el entorno de tiempo de compilación del lenguaje de
programación local. Esta facilidad, llamada DLinq, se aprovecha de la integración de la información
de esquemas SQL en los metadatos del CLR. Esta integración compila las definiciones de tablas y
vistas SQL dentro de tipos CLR que pueden ser accedidas desde cualquier lenguaje.
DLinq define dos atributos principales, [Table] y [Column], que indican qué tipos y propiedades del
CLR corresponden a datos SQL externos. El atributo [Table] puede ser aplicado a una clase y
asocia el tipo del CLR con una tabla o vista nombrada de SQL. El atributo [Column] puede ser
aplicado a cualquier campo o propiedad y asocia el miembro con una columna nombrada de SQL.
Ambos atributos tienen parámetros, para permitir el almacenamiento de metadatos específicos de
SQL. Por ejemplo, observe esta sencilla definición de esquema SQL:
create table People (
Name nvarchar(32) primary key not null,
Age int not null,
CanCode bit not null
)
[Column]
public int Age;
[Column]
public bool CanCode;
}
[Table(Name="Orders")]
public class Order {
[Column(DbType="nvarchar(32) not null", Id=true)]
public string OrderID;
[Column]
public int? Amount;
}
Observe de este ejemplo que las columnas que permiten valores nulos se mapean a tipos
msdn.microsoft.com/…/bb308959.aspx 20/24
14/12/2010 El proyecto LINQ
anulables del CLR (los tipos anulables aparecieron en la versión 2 de .NET Framework), y que para
los tipos de SQL que no tienen una correspondencia 1:1 con un tipo CLR (por ejemplo, nvarchar,
char, text), el tipo SQL original es memorizado en los metadatos del CLR.
Para ejecutar una consulta contra un almacén relacional, la implementación de DLinq del patrón
LINQ traduce la consulta de su árbol de expresión a una expresión SQL y un objeto DbCommand
de ADO.NET adecuado para la evaluación remota. Por ejemplo, observe esta simple consulta:
// establecer contexto de consulta sobre una conexión de ADO.NET
DataContext context = new DataContext(
"Initial Catalog=petdb;Integrated Security=sspi");
// construir la consulta
var query =
from c in custs
from o in orders
where o.Customer == c.Name
select new {
c.Name, o.OrderID, o.Amount, c.Age
};
// ejecutar la consulta
foreach (var item in query)
Console.WriteLine("{0} {1} {2} {3}",
item.Name, item.OrderID, item.Amount, item.Age);
El tipo DataContext ofrece un traductor ligero que se encarga de traducir los operadores de
consulta estándar a SQL. DataContext utiliza un objeto de ADO.NET IDbConnection existente para
acceder al almacén, y puede ser inicializado bien mediante un objeto que represente a una
conexión ya establecida o a una cadena de conexión que puede ser utilizada para crear una.
El método GetTable ofrece variables compatibles con IEnumerable que pueden ser utilizadas en
expresiones de consulta para representar la tabla o vista remota. Las llamadas a GetTable no
causan ninguna interacción con la base de datos – en vez de eso, representan el potencial de
interactuar con la tabla o vista remota utilizando expresiones de consulta. En nuestro ejemplo
anterior, la consulta no es transmitida al almacén de datos hasta que el programa itera sobre la
expresión de consulta, en este caso utilizando la sentencia foreach de C#. Cuando el programa
itera por primera vez sobre la consulta, la maquinaria del DataContext traduce el árbol de
expresión en la siguiente sentencia SQL que es enviada al almacén:
SELECT [t0].[Age], [t1].[Amount],
[t0].[Name], [t1].[OrderID]
FROM [Customers] AS [t0], [Orders] AS [t1]
WHERE [t1].[Customer] = [t0].[Name]
Principio de la página
msdn.microsoft.com/…/bb308959.aspx 21/24
14/12/2010 El proyecto LINQ
XLinq: Integración de XML
Las consultas integradas en los lenguajes .NET para XML (XLinq) permiten que los datos XML sean
consultados utilizando los operadores de consulta estándar, así como mediante operadores
específicos de árboles que ofrecen posibilidades de navegación por los descendientes, ancestros y
hermanos al estilo de XPath. Ofrecen una representación eficiente en memoria para XML que se
integra con la infraestructura de lectura/escritura de System.Xml existente y es más fácil de uasr
que el DOM de W3C. Tres tipos se encargan de la mayor parte del trabajo de integración de XML
con las consultas: XName, XElement y XAttribute.
XName suministra una manera sencilla de trabajar con los identificadores cualificados mediante
espacios de nombres (QNames) utilizados tanto en los nombres de elementos como de atributos.
XName gestiona de manera transparente la atomización eficiente de identificadores y permite que
se utilicen símbolos o simples cadenas dondequiera que se necesite un QName.
var s = e.ToString();
Observe que no es necesario ningún patrón fábrica basado en DOM para crear la expresión XML, y
que la implementación de ToString produce el XML textual. Los elementos XML también pueden
ser construidos a partir de un XmlReader o de un literal de cadena:
var e2 = XElement.Load(xmlReader);
var e1 = XElement.Parse(
@"<Person CanCode='true'>
<Name>Loren David</Name>
<Age>31</Age>
</Person>");
XElement también soporta la emisión de XML utilizando el tipo existente XmlWriter.
XElement conecta con los operadores de consulta, permitiendo a los desarrolladores escribir
consultas contra información no basada en XML y producir resultados XML construyendo objetos
XElement en el cuerpo de la cláusula select:
var query = from p in people
where p.CanCode
select new XElement("Person",
new XAttribute("Age", p.Age),
p.Name);
Esta consulta devuelve una secuencia de XElement. Para permitir que los objetos XElement se
construyan a partir del resultado de esta clase de consultas, el constructor de XElement permite
que se le pase directamente como argumento una secuencia de elementos:
var x = new XElement("People",
from p in people
where p.CanCode
msdn.microsoft.com/…/bb308959.aspx 22/24
14/12/2010 El proyecto LINQ
select
new XElement("Person",
new XAttribute("Age", p.Age),
p.Name));
La sentencia anterior tiene una traducción directa a Visual Basic. Sin embargo, Visual Basic 9.0
también soporta el uso de literales XML, que permiten que las expresiones de consulta sean
expresadas utilizando una sintaxis XML declarativa directamente dentro de Visual Basic. El ejemplo
anterior pudo haberse construido con la sentencia de Visual Basic:
Dim x = _
<People>
<%= From p In people __
Select <Person Age=<%= p.Age %>>p.Name</Person> _
Where p.CanCode _
%>
</People>
Los ejemplos hasta el momento han mostrado cómo construir nuevos valores XML utilizando
consultas integradas en los lenguajes. Los tipos XElement y XAttribute también simplifican la
extracción de información de estructuras XML. XElement ofrece métodos de acceso que permiten
aplicar expresiones de consulta a los ejes tradicionales de XPath. Por ejemplo, la siguiente
consulta extrae únicamente los nombres del XElement mostrado antes:
IEnumerable<string> justNames =
from e in x.Descendants("Person")
select e.Value;
Para extraer valores estructurados del XML, simplemente utilizamos una expresión de inicializador
en nuestra cláusula select:
IEnumerable<Person> persons =
from e in x.Descendants("Person")
select new Person {
Name = e.Value,
Age = (int)e.Attribute("Age")
};
Observe que tanto XAttribute como XElement soportan conversiones explícitas para extraer el
valor textual como un tipo primitivo. Para gestionar los datos ausentes, podemos simplemente
convertir a un tipo anulable:
IEnumerable<Person> persons =
from e in x.Descendants("Person")
select new Person {
Name = e.Value,
Age = (int?)e.Attribute("Age") ?? 21
};
En este caso, se utiliza un valor predeterminado de 21 cuando el atributo Age está ausente.
Visual Basic 9.0 ofrece soporte directo en el lenguaje para los métodos de acceso Elements,
Attribute y Descendants de XElement, permitiendo que los datos basados en XML sean accedidos
utilizando una sintaxis más compacta y directa llamada propiedades de los ejes XML. Podemos
utilizar esta funcionalidad para escribir la sentencia C# anterior de la siguiente forma:
msdn.microsoft.com/…/bb308959.aspx 23/24
14/12/2010 El proyecto LINQ
Dim persons = _
From e In x...<Person> _
Select new Person { _
.Name = e.Value, _
.Age = e.@Age.Value ?? 21 _
}
En Visual Basic, x...<Person> recupera todos los elementos en la colección Descendants de x que
tienen el nombre Person, mientras que la expresión e.@Age encuentra todos los XAttributes con el
nombre Age. La propiedad Value obtiene el primer atributo de la colección y llama a la propiedad
Value de ese atributo.
Principio de la página
Conclusión
Las consultas integradas en los lenguajes .NET añaden capacidades de consulta al CLR y a los
lenguajes orientados a él. La facilidad de consulta se apoya en las expresiones lambda y los
árboles de expresiones para permitir que los predicados, proyecciones y expresiones de extracción
de claves puedan utilizarse como código ejecutable opaco o como datos transparentes en
memoria adecuados para su tratamiento posterior o traducción. Los operadores de consulta
estándar definidos por el Proyecto LINQ operan sobre cualquier fuente de información basada en
IEnumerable<T>, y se integran con ADO.NET (DLinq) y System.Xml (XLinq) para permitir que los
datos relacionales y XML obtengan los beneficios de las consultas integradas en los lenguajes.
Principio de la página
msdn.microsoft.com/…/bb308959.aspx 24/24