Sie sind auf Seite 1von 24

14/12/2010 El proyecto LINQ

El proyecto LINQ

Mayo de 2006

Publicado: 18 de diciembre de 2006

Traducido por Octavio Hernández

Este artículo no ha sido traducido por Microsoft

Este artículo y la veracidad de su traducción no ha sido revisado o verificado por Microsoft.

Microsoft no acepta responsabilidades sobre la veracidad o la información de este artículo que se


proporciona tal cual por la comunidad.

En esta página

Consultas integradas en los lenguajes .NET


Introducción a los operadores de consulta estándar
Características del lenguaje que dan soporte al Proyecto LINQ
Expresiones lambda y árboles de expresiones
Métodos extensores
Evaluación diferida de consultas
La interfaz IQueryable-T-
Inicialización de valores compuestos
Valores y tipos estructurados
Más operadores de consulta estándar
Ordenación y agrupación
Select vs. SelectMany
Operadores de acumulación
Operadores de encuentro
Sintaxis de consultas
DLinq: Integración de SQL
XLinq: Integración de XML
Conclusión

Consultas integradas en los lenguajes .NET

Después de dos décadas, la industria ha alcanzado un punto estable en la evolución de las


tecnologías de programación orientada a objetos. Los programadores ahora están familiarizados
con conceptos como las clases, objetos y métodos. Analizando la generación de tecnologías
actual y siguiente, se hace evidente que el siguiente gran reto para la tecnología de la
programación es reducir la complejidad del acceso e integrar la información que no se define de
manera nativa utilizando la tecnología orientada a objetos. Las dos fuentes de información no
orientadas a objetos más comunes son las bases de datos relacionales y XML.

En vez de añadir características específicas para el tratamiento de datos relacionales o XML a


nuestros lenguajes de programación y motor de ejecución, con el proyecto LINQ hemos seguido
un enfoque más general, y estamos añadiendo a .NET Framework facilidades de consulta de
propósito general aplicables a todas las fuentes de información, y no solo a los datos relacionales
o XML. Esta facilidad se llama ‘Consultas integradas en los lenguajes’ (Language Integrated Query
- LINQ).

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.

La extensibilidad de la arquitectura de consultas es aprovechada por el propio proyecto LINQ para


ofrecer implementaciones que operan sobre datos XML y SQL. Los operadores de consulta sobre
XML (XLinq) utilizan una facilidad de XML en memoria interna eficiente y fácil de usar para ofrecer
funcionalidad XPath/XQuery dentro del lenguaje de programación huésped. Los operadores de
consulta sobre datos relacionales (DLinq) se apoyan en la integración de definiciones de esquemas
basadas en SQL en el sistema de tipos del CLR. Esta integración ofrece un fuerte control de tipos
sobre los datos relacionales, a la vez que mantiene la potencia expresiva del modelo relacional y el
rendimiento de la evaluación de las consultas directamente en el almacén de datos subyacente.

Principio de la página

Introducción a los operadores de consulta estándar

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

IEnumerable<string> expr = from s in names


where s.Length == 5
orderby s
select s.ToUpper();

foreach (string item in expr)


Console.WriteLine(item);
}
}

Si se compila y se ejecuta este programa, se obtendrá la siguiente salida:


BURKE
DAVID
FRANK

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

Características del lenguaje que dan soporte al Proyecto LINQ

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

Expresiones lambda y árboles de expresiones

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

IEnumerable<string> expr = names.Where(filter)


.OrderBy(extract)
.Select(project);

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

Func<string, string> extract = delegate (string s) {


return s;
};

Func<string, string> project = delegate (string s) {


return s.ToUpper();
};

IEnumerable<string> expr = names.Where(filter)


.OrderBy(extract)
.Select(project);

En general, el desarrollador es libre de utilizar métodos nombrados, métodos anónimos o


expresiones lambda con los operadores de consulta. Las expresiones lambda tienen la ventaja de
ofrecer la sintaxis más directa y compacta para la escritura. Y lo que es más importante, las
expresiones lambda pueden ser compiladas como código o como datos, lo que permite que las
expresiones lambda puedan ser tratadas en tiempo de ejecución por optimizadores, traductores y
evaluadores.

LINQ define un tipo distinguido, Expression<T> (en el espacio de nombres System.Expressions),


que indica que se desea obtener un árbol de expresión para una expresión lambda dada en vez de
un cuerpo de método tradicional basado en IL. Los árboles de expresiones son representaciones
eficientes en memoria de las expresiones lambda y hacen la estructura de las expresiones
transparente y explícita.

La determinación de si el compilador emitirá código IL ejecutable o un árbol de expresión viene


dada por cómo se utiliza la expresión lambda. Cuando una expresión lambda es asignada a una
variable, campo o parámetro cuyo tipo es un delegado, el compilador emite código IL que es
idéntico al de un método anónimo. Cuando una expresión lambda es asignada a una variable,
campo o parámetro cuyo tipo es Expression<T>, el compilador emite un árbol de expresión.

Por ejemplo, considere las dos siguientes declaraciones de variables:


Func<int, bool> f = n => n < 5;
Expresión<Func<int, bool>> e = n => n < 5;

La variable f es una referencia a un delegado directamente ejecutable:


bool isSmall = f(2); // isSmall es ahora true

La variable e es una referencia a un árbol de expresión que no es directamente ejecutable:


bool isSmall = e(2); // error de compilación, expresión == datos

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;

BinaryExpression body = (BinaryExpression)filter.Body;

msdn.microsoft.com/…/bb308959.aspx 4/24
14/12/2010 El proyecto LINQ
ParameterExpression left = (ParameterExpression)body.Left;
ConstantExpression right = (ConstantExpression)body.Right;

Console.WriteLine("{0} {1} {2}",


left.Name, body.NodeType, right.Value);

descompone el árbol de expresión en tiempo de ejecución e imprime la cadena:


n LT 5

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;

public static class Sequence {


public static IEnumerable<T> Where<T>(
this IEnumerable<T> source,
Func<T, bool> predicate) {

foreach (T item in source)


if (predicate(item))
yield return item;
}
}
}

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 operadores de consulta estándar se definen como métodos extensores en el tipo


System.Query.Sequence. Al examinar los operadores de consulta estándar, observará que todos
excepto unos pocos están definidos en términos de la interfaz IEnumerable<T>. Esto significa que
cada fuente de información compatible con IEnumerable<T> obtiene los operadores de consulta
estándar simplemente agregando la siguiente sentencia using en C#:
using System.Query; // hace visibles los operadores de consulta

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

public IEnumerable<int> Where(Func<int, bool> filter) {


for (int i = 1; i <= 10; i++)
if (filter(i))
yield return i;
}
}

Dada la definición de clase anterior, el siguiente programa:


MySequence s = new MySequence();
foreach (int item in s.Where(n => n > 3))
Console.WriteLine(item);

utilizará la implementación de MySequence.Where y no el método extensor, ya que los métodos


de instancia tienen precedencia sobre los métodos extensores.

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

// "moderna" – puede utilizarse directamente con los


// operadores de consulta
IEnumerable<object> modern = classic.OfType<object>();

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

Al enumerar la variable justStrings en una sentencia foreach, obtendremos la secuencia formada


por las dos cadenas “Hello” y “World”.

Principio de la página

Evaluación diferida de consultas

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.

En muchas aplicaciones, éste es exactamente el comportamiento deseado. Para las aplicaciones


que deseen guardar en caché los resultados de la evaluación de una consulta, se ofrecen dos
operadores, ToList y ToArray, que fuerzan la evaluación inmediata de una consulta y devuelven
un objeto List<T> o un array que contiene los resultados de la evaluación de la consulta.

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 declara una variable que representa una consulta


IEnumerable<string> ayes = names.Where(s => s[0] == 'A');

// se evalúa la consulta
foreach (string item in ayes)
Console.WriteLine(item);

// se modifica la fuente de información original


names[0] = "Bob";

msdn.microsoft.com/…/bb308959.aspx 7/24
14/12/2010 El proyecto LINQ

// se evalúa la consulta otra vez; esta vez no estará "Allen"


foreach (string item in ayes)
Console.WriteLine(item);

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

// se declara una variable que representa el resultado


// de la evaluación inmediata de una consulta
string[] ayes = names.Where(s => s[0] == 'A').ToArray();

// se itera sobre los resultados cacheados


foreach (string item in ayes)
Console.WriteLine(item);

// modificar la fuente original no afecta a ayes


names[0] = "Bob";

// se itera sobre el resultado otra vez; aún estará "Allen"


foreach (string item in ayes)
Console.WriteLine(item);

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

Inicialización de valores compuestos

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;

public string Name {


get { return name; } set { name = value; }
}

public int Age {


get { return age; } set { age = value; }
}

public bool CanCode {


get { return canCode; } set { canCode = value; }
}
}

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

Semánticamente, esta sentencia es equivalente a la siguiente secuencia de sentencias:


Person value = new Person();
value.Name = "Chris Smith";
value.Age = 31;
value.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
});

La sintaxis de inicialización de objetos es también conveniente para inicializar arrays de valores


estructurados. Por ejemplo, observe cómo esta variable de tipo array se inicializa mediante
inicializadores de objetos individuales:
static Person[] people = {
new Person { Name="Allen Frances", Age=11, CanCode=false },
new Person { Name="Burke Madison", Age=50, CanCode=true },
new Person { Name="Connor Morgan", Age=59, CanCode=false },
new Person { Name="David Charles", Age=33, CanCode=true },
new Person { Name="Everett Frank", Age=16, CanCode=true },
};

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

object v2 = new { // observe la omisión del nombre del tipo


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

En el ejemplo anterior, la variable value es de un tipo anónimo cuya definición es equivalente al


siguiente seudo-C#:
internal class ??? {
string _Name;
int _Age;
bool _CanCode;

msdn.microsoft.com/…/bb308959.aspx 10/24
14/12/2010 El proyecto LINQ
public string Name {
get { return _Name; } set { _Name = value; }
}

public int Age{


get { return _Age; } set { _Age = value; }
}

public bool CanCode {


get { return _CanCode; } set { _CanCode = value; }
}

public bool Equals(object obj) { … }

public bool GetHashCode() { … }


}

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

var couple = new {


Husband = new { bob.Name, bob.Age },
Wife = new { Name = jane.FirstName, jane.Age }
};

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

foreach (var item in expr)


Console.WriteLine("{0} es un {1} coder",
item.Name,
item.BadCoder ? "bad" : "good");

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

Más operadores de consulta estándar

Sobre las facilidades de consulta básicas descritas anteriormente, se ha definido un conjunto de


operadores que ofrecen maneras útiles de manipular secuencias y componer consultas, dando al
usuario un alto nivel de control sobre el resultado dentro del marco de trabajo conveniente de los
operadores de consulta estándar.

Principio de la página

Ordenación y agrupación

En general, la evaluación de una expresión de consulta da como resultado una secuencia de


valores que se producen en un orden que es intrínseco a las fuentes de información subyacentes.
Para dar a los desarrolladores un control explícito sobre el orden en que los valores son
producidos, se han definido operadores de consulta estándar para controlar ese orden. El más
básico de estos operadores es el operador OrderBy.

Los operadores OrderBy y OrderByDescending pueden ser aplicados a cualquier fuente de


información y permitir al usuario suministrar una función de extracción de clave que produce el
valor utilizado para ordenar los resultados. OrderBy y OrderByDescending también aceptan una
función de comparación opcional que puede ser utilizada para imponer un orden parcial sobre las
claves. Veamos un ejemplo básico:
string[] names = { "Burke", "Connor", "Frank", "Everett",
"Albert", "George", "Harris", "David" };

// ordenación de identidad
var s1 = names.OrderBy(s => s);
var s2 = names.OrderByDescending(s => s);

// ordenación por longitud


var s3 = names.OrderBy(s => s.Length);
var s4 = names.OrderByDescending(s => s.Length);

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

var s1 = names.OrderBy(s => s.Length).ThenBy(s => s);

La evaluación de la consulta referenciada mediante s1 en este ejemplo producirá la siguiente


secuencia de valores:
"Burke", "David", "Frank",
"Albert", "Connor", "George", "Harris",

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

La aplicación más simple de GroupBy es similar a la siguiente:string[] names = { "Albert", "Burke",


"Connor", "David",
"Everett", "Frank", "George", "Harris"};

// agrupar por longitud


var groups = names.GroupBy(s => s.Length);

foreach (IGrouping<int, string> group in groups) {


Console.WriteLine("Strings of length {0}", group.Key);

foreach (string value in group)


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

Al ser ejecutado, este programa imprime lo siguiente:


Strings of length 6
Albert
Connor
George
Harris
Strings of length 5
Burke
David
Frank
Strings of length 7
Everett

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

// agrupar por longitud


var groups = names.GroupBy(s => s.Length, s => s[0]);
foreach (IGrouping<int, char> group in groups) {
Console.WriteLine("Strings of length {0}", group.Key);

foreach (char value in group)


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

Esta variante imprime lo siguiente:


Strings of length 6
msdn.microsoft.com/…/bb308959.aspx 13/24
14/12/2010 El proyecto LINQ
A
C
G
H
Strings of length 5
B
D
F
Strings of length 7
E

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

Select vs. SelectMany

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;

foreach (T element in source)


result = func(result, element);

return result;
}

El operador Aggregate simplifica la realización de un cálculo sobre una secuencia de valores.


Aggregate llama a la expresión lambda una vez para cada miembro de la secuencia subyacente.
Cada vez que Aggregate llama a la expresión lambda, le pasa el miembro de la secuencia y un
valor acumulado (cuyo valor inicial viene dado por el parámetro seed de Aggregate). El resultado
de la evaluación de la expresión lambda sustituye al valor acumulado anterior, y Aggregate
devuelve el resultado final de la expresión lambda.

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

int count = names.Aggregate(0, (c, s) => c + s.Length);


// count == 46

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

int total1 = numbers.Sum(); // total1 == 55

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

var tokens = text.Select(s => s.Split(' '));

foreach (string[] line in tokens)


foreach (string token in line)
Console.Write("{0}.", token);

Al ser ejecutado, este programa imprime el siguiente texto:


Albert.was.here.Burke.slept.late.Connor.is.happy.
Idealmente, nos habría gustado que nuestra consulta hubiera devuelto una secuencia “aplanada”
de palabras y no expusiera el string[] intermedio al consumidor. Para lograr esto, se puede utilizar
el operador SelectMany en vez del operador Select. El operador SelectMany funciona de una
manera similar al operador Select. Se diferencia en que espera que la función de transformación
devuelva una secuencia, que será entonces expandida por el operador SelectMany. Este es
nuestro programa rescrito utilizando SelectMany:
string[] text = { "Albert was here",
"Burke slept late",
"Connor is happy" };

var tokens = text.SelectMany(s => s.Split(' '));

foreach (string token in tokens)


Console.Write("{0}.", token);

La utilización de SelectMany provoca que cada secuencia intermedia sea expandida como parte
de la evaluación normal.

SelectMany es ideal para combinar dos fuentes de información:


string[] names = { "Burke", "Connor", "Frank", "Everett",
"Albert", "George", "Harris", "David" };

var query = names.SelectMany(n =>


people.Where(p => n.Equals(p.Name))
);

En la expresión lambda pasada a SelectMany, la consulta anidada se aplica a una fuente de


información diferente, pero tiene dentro de su ámbito al parámetro n pasado de la fuente más
externa. Por lo tanto, people.Where es llamado una vez para cada n, con las secuencias
resultantes aplanadas por SelectMany para el resultado final. El resultado es una secuencia de
todas las personas cuyo nombre aparece en el array names.

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

var query = names.Join(people, n => n, p => p.Name, (n,p) => p);

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

var query = names.GroupJoin(people, n => n, p => p.Name,


(n, matching) =>
new { Name = n, Count = matching.Count() }
);

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.

Comencemos examinando la sencilla consulta con la que comenzamos este documento:


IEnumerable<string> expr = names
.Where(s => s.Length == 5)
.OrderBy(s => s)
.Select(s => s.ToUpper());

Utilizando la sintaxis de consultas, podemos rescribir esta sentencia de la siguiente forma:


IEnumerable<string> expr = from s in names
where s.Length == 5
orderby s
select s.ToUpper();

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.

Comencemos analizando la estructura básica de una expresión de consulta. Cada expresión de


consulta en C# comienza con una cláusula from y termina con una cláusula select o group. La
cláusula from inicial puede ir seguida de cero o más cláusulas from, let o where. Adicionalmente,
cualquier cantidad de cláusulas join puede ir inmediatamente después de una cláusula from. Cada
cláusula from es un generador que introduce una variable de iteración sobre una secuencia, cada
cláusula let da nombre al resultado de una expresión, y cada cláusula where es un filtro que
excluye elementos del resultado. Cada cláusula join combina una nueva fuente de datos con los
resultados de un from o join anterior. La cláusula final select o group puede ir precedida de una
cláusula orderby que especifica una ordenación para el resultado:
expresión-de-consulta ::= cláusula-from cuerpo-consulta

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-from ::= from nombreElemento in exprFuente

cláusula-join ::=
join nombreElemento in exprFuente
on exprClave equals exprClave
(into nombreElemento)?

cláusula-let ::= let nombreElemento = exprSelección

cláusula-where ::= where predExpr

cláusula-orderby ::= orderby


(exprClave (ascending | descending)?)*

cláusula-select ::= select exprSelección

cláusula-groupby ::= group exprSelección by exprClave

continuación-consulta ::= into nombreElemento cuerpo-consulta

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

var query2 = from p in people


where p.Age > 20
orderby p.Age descending, p.Name
group new {
p.Name, Senior = p.Age > 30, p.CanCode
} by 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;

Cuando es ejecutada contra el array de entrada:


string[] names = { "Burke", "Connor", "Frank", "Everett",
"Albert", "George", "Harris", "David" };

obtenemos los siguientes resultados:


Burke Burke
Frank Frank
David David

La expresión de consulta anterior se expande a la siguiente expresión con notación de punto:


var query = names.Where(s1 => s1.Length == 5)
.SelectMany(s1 =>
names.Where(s2 => s1 == s2)
.Select(s2 => 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;

foreach (var group in query) {


Console.WriteLine("Strings of length {0}", group.Key);

foreach (var val in group.Group)


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

Este programa imprime lo siguiente:


Strings of length 7
Everett
Strings of length 6
Albert
Connor
George
Harris
Strings of length 5
Burke
David
Frank

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

DLinq: Integración de SQL

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
)

create table Orders (


OrderID nvarchar(32) primary key not null,
Customer nvarchar(32) not null,
Amount int
)

Su equivalente CLR tendría la siguiente apariencia:


[Table(Name="People")]
public class Person {
[Column(DbType="nvarchar(32) not null", Id=true)]
public string Name;

[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(DbType="nvarchar(32) not null")]


public string Customer;

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

// obtener variables que representan las tablas remotas


// que corresponden a los tipos Person y Order del CLR
Table<Person> custs = context.GetTable<Person>();
Table<Order> orders = context.GetTable<Order>();

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

Es importante destacar que incorporando directamente la posibilidad de consultas en el lenguaje


de programación local, los desarrolladores obtienen toda la potencia del modelo relacional sin
tener que “cocer” estáticamente las relaciones dentro del tipo del CLR. Dicho esto, un mapeado
objeto/relacional completo podría también aprovechar esta capacidad básica de consulta para
aquellos usuarios que deseen tal funcionalidad. DLinq ofrece una funcionalidad de mapeado
objeto/relacional con la que el desarrollador puede definir y navegar por las relaciones entre
objetos. Usted puede referirse a Orders (Pedidos) como una propiedad de la clase Customer
(Cliente) utilizando un mapeado, de modo que no se necesite un encuentro explícito para
combinar ambas. Los ficheros de mapeado externos permiten separar el mapeado del modelo de
objetos para obtener unas posibilidades más amplias de mapeado.

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.

Los elementos y atributos XML se representan mediante XElement y XAttribute, respectivamente.


XElement y XAttribute soportan la sintaxis normal de construcción, permitiendo a los
desarrolladores escribir expresiones XML usando una sintaxis natural:
var e = new XElement("Person",
new XAttribute("CanCode", true),
new XElement("Name", "Loren David"),
new XElement("Age", 31));

var s = e.ToString();

Esto corresponde al siguiente XML:


<Person CanCode="true">
<Name>Loren David</Name>
<Age>31</Age>
</Person>

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

Esta expresión XML resulta en el siguiente XML:


<People>
<Person Age="11">Allen Frances</Person>
<Person Age="59">Connor Morgan</Person>
</People>

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;

//justNames = ["Allen Frances", "Connor Morgan"]

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

Das könnte Ihnen auch gefallen