El proyecto LINQ

24
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 14/12/2010 El proyecto LINQ msdn.microsoft.com/…/bb308959.aspx 1/24

Transcript of El proyecto LINQ

Page 1: 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 seproporciona 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 lastecnologías de programación orientada a objetos. Los programadores ahora están familiarizadoscon conceptos como las clases, objetos y métodos. Analizando la generación de tecnologíasactual y siguiente, se hace evidente que el siguiente gran reto para la tecnología de laprogramación es reducir la complejidad del acceso e integrar la información que no se define demanera nativa utilizando la tecnología orientada a objetos. Las dos fuentes de información noorientadas 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 anuestros lenguajes de programación y motor de ejecución, con el proyecto LINQ hemos seguidoun enfoque más general, y estamos añadiendo a .NET Framework facilidades de consulta depropósito general aplicables a todas las fuentes de información, y no solo a los datos relacionaleso 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 unacaracterí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

14/12/2010 El proyecto LINQ

msdn.microsoft.com/…/bb308959.aspx 1/24

Page 2: El proyecto LINQ

se beneficien de los metadatos ricos, verificación de sintaxis en tiempo de compilación, tipadoestático y ayuda IntelliSense que antes estaban disponibles solo para el código imperativo. Lasconsultas integradas en los lenguajes también hacen posible aplicar una única facilidad declarativade propósito general a toda la información en memoria, y no solo a la información proveniente defuentes externas.

Las consultas integradas en los lenguajes .NET definen un conjunto de operadores de consultaestándar de propósito general que hacen posible que las operaciones de recorrido, filtro yproyección sean expresadas de una manera directa pero declarativa en cualquier lenguaje deprogramación. Los operadores de consulta estándar permiten aplicar las consultas a cualquierfuente de información basada en IEnumerable<T>. LINQ permite que terceros fabricantesaumenten el conjunto de operadores de consulta estándar, añadiendo los operadores de dominioespecífico que sean apropiados para el dominio o la tecnología de destino. Más importante aún esque terceros fabricantes también pueden reemplazar los operadores de consulta estándar con suspropias 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, talesimplementaciones gozarán de la misma integración en los lenguajes y soporte de herramientas quelos operadores de consulta estándar.

La extensibilidad de la arquitectura de consultas es aprovechada por el propio proyecto LINQ paraofrecer implementaciones que operan sobre datos XML y SQL. Los operadores de consulta sobreXML (XLinq) utilizan una facilidad de XML en memoria interna eficiente y fácil de usar para ofrecerfuncionalidad XPath/XQuery dentro del lenguaje de programación huésped. Los operadores deconsulta sobre datos relacionales (DLinq) se apoyan en la integración de definiciones de esquemasbasadas en SQL en el sistema de tipos del CLR. Esta integración ofrece un fuerte control de tipossobre los datos relacionales, a la vez que mantiene la potencia expresiva del modelo relacional y elrendimiento 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 sencilloprograma en C# 3.0 que utiliza los operadores de consulta estándar para procesar el contenido deun 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:

BURKEDAVIDFRANK

14/12/2010 El proyecto LINQ

msdn.microsoft.com/…/bb308959.aspx 2/24

Page 3: El proyecto LINQ

Para comprender cómo funcionan las consultas integradas en los lenguajes, debemos analizar laprimera 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 consultaopera sobre una o más fuentes de información aplicando uno o más operadores de consulta, yasean operadores de consulta estándar u operadores específicos de un dominio. Esta expresiónutiliza 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 declarativaconveniente más concisa que el código que se podría escribir manualmente. Las sentenciasanteriores 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 expresioneslambda, que son fragmentos de código muy similares a los delegados. Ellas permiten que losoperadores de consulta estándar se definan individualmente como métodos y se conectenutilizando notación de punto. Conjuntamente, estos métodos conforman las bases de un lenguajede 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ísticastiene su utilidad propia, pero conjuntamente, estas características ofrecen una manera extensiblede definir consultas y API de consultas. En esta sección exploraremos estas características de loslenguajes 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 lasexpresiones lambda, que ofrecen a los desarrolladores una manera conveniente de escribirfunciones que pueden ser pasadas como argumentos para su evaluación subsiguiente. Lasexpresiones lambda son similares a los delegados del CLR y deben adherirse a una firma de métododefinida por un tipo delegado. Para ilustrar esto, podemos expandir la sentencia anterior a otraforma 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();

14/12/2010 El proyecto LINQ

msdn.microsoft.com/…/bb308959.aspx 3/24

Page 4: 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 oexpresiones lambda con los operadores de consulta. Las expresiones lambda tienen la ventaja deofrecer la sintaxis más directa y compacta para la escritura. Y lo que es más importante, lasexpresiones lambda pueden ser compiladas como código o como datos, lo que permite que lasexpresiones lambda puedan ser tratadas en tiempo de ejecución por optimizadores, traductores yevaluadores.

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 deun cuerpo de método tradicional basado en IL. Los árboles de expresiones son representacioneseficientes en memoria de las expresiones lambda y hacen la estructura de las expresionestransparente y explícita.

La determinación de si el compilador emitirá código IL ejecutable o un árbol de expresión vienedada por cómo se utiliza la expresión lambda. Cuando una expresión lambda es asignada a unavariable, campo o parámetro cuyo tipo es un delegado, el compilador emite código IL que esidé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. Porejemplo, este programa:

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

BinaryExpression body = (BinaryExpression)filter.Body;

14/12/2010 El proyecto LINQ

msdn.microsoft.com/…/bb308959.aspx 4/24

Page 5: 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 hacerposible un ecosistema de librerías de terceros que aprovechen las abstracciones de consultabásicas que son parte de la plataforma. La implementación del acceso a datos de DLinq aprovechaesta capacidad para traducir los árboles de expresiones a sentencias T-SQL que pueden serevaluadas 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étodosextensores son otra. Los métodos extensores combinan la flexibilidad del “tipado de pato” que hanhecho populares los lenguajes dinámicos con el rendimiento y la validación en tiempo decompilación de los lenguajes de tipado estático. Mediante los métodos extensores, tercerosfabricantes pueden aumentar el contrato público de un tipo con nuevos métodos, permitiendo a lavez a autores de tipos individuales ofrecer su propia implementación especializada de esosmétodos.

Los métodos extensores se definen como métodos estáticos en clases estáticas, pero se marcancon el atributo [System.Runtime.CompilerServices.Extension] en los metadatos del CLR. Sepropone que los lenguajes ofrezcan una sintaxis directa para los métodos extensores. En C#, losmétodos extensores se indican mediante el modificador this aplicado al primer parámetro delmé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. Enel ejemplo anterior, el método extensor Where extiende el tipo IEnumerable<T>. Debido a queWhere es un método estático, podemos llamarlo directamente como a cualquier otro métodoestá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 llamadosutilizando sintaxis de instancia:

IEnumerable<string> expr = names.Where(s => s.Length < 6);

14/12/2010 El proyecto LINQ

msdn.microsoft.com/…/bb308959.aspx 5/24

Page 6: El proyecto LINQ

Los métodos extensores se resuelven en tiempo de compilación sobre la base de qué métodosextensores están en ámbito. Cuando un espacio de nombres en importado mediante la sentenciausing de C# o la sentencia Import de VB, todos los métodos extensores definidos por las clasesestá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 tipoSystem.Query.Sequence. Al examinar los operadores de consulta estándar, observará que todosexcepto unos pocos están definidos en términos de la interfaz IEnumerable<T>. Esto significa quecada fuente de información compatible con IEnumerable<T> obtiene los operadores de consultaestá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íficopueden (a) definir sus propios métodos con los mismos nombres y firmas compatibles en el tipoespecífico o (b) definir nuevos métodos extensores con los mismos nombres que extiendan el tipoespecífico. Los usuarios que quieran esquivar totalmente los operadores de consulta estándarpueden simplemente no poner en ámbito System.Query y escribir sus propios métodos extensorespara 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. Estopermite que los tipos definidos por el usuario ofrezcan sus propios operadores de consulta quetengan precedencia sobre los operadores estándar. Por ejemplo, observe la siguiente colecciónpersonalizada:

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étodosde instancia tienen precedencia sobre los métodos extensores.

El operador OfType es uno de los pocos operadores de consulta estándar que no extienden unafuente de información basada en IEnumerable<T>. Echemos un vistazo al operador de consultaOfType:

public static IEnumerable<T> OfType<T>(this IEnumerable source) { foreach (object item in source) if (item is T) yield return (T)item;}

14/12/2010 El proyecto LINQ

msdn.microsoft.com/…/bb308959.aspx 6/24

Page 7: El proyecto LINQ

OfType acepta no solamente fuentes basadas en IEnumerable<T>, sino también fuentesprogramadas contra la versión no parametrizada de la interfaz IEnumerable que estaba disponibleen la versión 1 de .NET Framework. El operador OfType permite a los usuarios aplicar losoperadores de consulta estándar a colecciones clásicas de .NET como la siguiente:

// "clásica" – no puede utilizarse directamente con los// operadores de consultaIEnumerable classic = new OlderCollectionType();

// "moderna" – puede utilizarse directamente con los // operadores de consultaIEnumerable<object> modern = classic.OfType<object>();

En este ejemplo, la variable modern produce la misma secuencia de valores que classic, pero sutipo es compatible con el código moderno basado en IEnumerable<T>, incluyendo los operadoresde consulta estándar.

El operador OfType es también útil para nuevas fuentes de información, ya que permite el filtradode los valores provenientes de una fuente en base al tipo. Al producir la nueva secuencia, OfTypesimplemente omite los miembros de la secuencia original que no son compatibles con el argumentode 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 formadapor 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 implementautilizando la construcción yield introducida en C# 2.0. Esta técnica de implementación es comúnpara todos los operadores estándar que devuelven secuencias de valores. El uso de yield ofreceun beneficio interesante, que consiste en que la consulta no es realmente evaluada hasta que seitera sobre ella, ya sea mediante una sentencia foreach o manualmente, utilizando los métodosGetEnumerator y MoveNext subyacentes. Esta evaluación diferida permite que las consultas semantengan 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 aplicacionesque deseen guardar en caché los resultados de la evaluación de una consulta, se ofrecen dosoperadores, ToList y ToArray, que fuerzan la evaluación inmediata de una consulta y devuelvenun 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 unaconsulta sencilla sobre un array:

// se declara una variable que contiene varias cadenasstring[] names = { "Allen", "Arthur", "Bennett" };

// se declara una variable que representa una consultaIEnumerable<string> ayes = names.Where(s => s[0] == 'A');

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

// se modifica la fuente de información originalnames[0] = "Bob";

14/12/2010 El proyecto LINQ

msdn.microsoft.com/…/bb308959.aspx 7/24

Page 8: 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 requiereuna copia cacheada de los resultados, debemos simplemente añadir un operador ToList o ToArraya la consulta de la siguiente forma:

// se declara una variable que contiene varias cadenasstring[] names = { "Allen", "Arthur", "Bennett" };

// se declara una variable que representa el resultado// de la evaluación inmediata de una consultastring[] ayes = names.Where(s => s[0] == 'A').ToArray();

// se itera sobre los resultados cacheadosforeach (string item in ayes) Console.WriteLine(item);

// modificar la fuente original no afecta a ayesnames[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álidopara 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 queimplementan la funcionalidad de consultas mediante árboles de expresiones, como DLinq. Estasfuentes de datos pueden beneficiarse de implementar la interfaz IQueryable<T>, para la cualtodos los operadores de consulta requeridos por el patrón LINQ se implementan utilizando árbolesde expresiones. Cada IQueryable<T> tiene una representación de “el código necesario paraejecutar la consulta” en la forma de un árbol de expresión. Todos los operadores de consultadiferidos devuelven un nuevo IQueryable que aumenta ese árbol de expresión con unarepresentación de una llamada a ese operador de consulta. Por esta razón, cuando llega elmomento de evaluar la consulta, generalmente porque el IQueryable es enumerado, la fuente dedatos 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 deconsulta 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 diferidaimplementando 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ónremotas. Esto no solo les permite escribir consultas polimórficas que pueden ser utilizadas contradiferentes fuentes de datos, sino que también abre la posibilidad de escribir consultas que operenentre 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 lasconsultas que simplemente filtran los miembros de una secuencia de valores. La mayoría de las

14/12/2010 El proyecto LINQ

msdn.microsoft.com/…/bb308959.aspx 8/24

Page 9: El proyecto LINQ

expresiones de consulta realizan además una proyección de esos miembros, transformandoefectivamente los miembros de la secuencia original en miembros cuyo valor y tipo puede sediferente del original. Para dar soporte a tales transformaciones, LINQ se apoya en una nuevaconstrucción conocida como expresiones de inicialización de objetos para crear nuevas instanciasde tipos estructurados. Para el resto de este documento, asumiremos que se ha definido elsiguiente 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 enlos campos y propiedades públicas de un tipo. Por ejemplo, para crear un nuevo valor de tipoPerson 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 consultasintegradas en los lenguajes, ya que permiten la construcción de nuevos valores estructurados encontextos en los que solo se permiten expresiones (como dentro de expresiones lambda y árbolesde expresiones). Por ejemplo, observe esta expresión de consulta que crea un nuevo objeto detipo 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 valoresestructurados. Por ejemplo, observe cómo esta variable de tipo array se inicializa medianteinicializadores 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

14/12/2010 El proyecto LINQ

msdn.microsoft.com/…/bb308959.aspx 9/24

Page 10: El proyecto LINQ

Valores y tipos estructurados

El Proyecto LINQ soporta un estilo de programación centrado en datos en el que algunos tiposexisten principalmente para suministrar una “forma” estática de un valor estructurado en vez deun objeto completo con su estado y comportamiento. Llevando esta premisa a su conclusiónlógica, frecuentemente ocurre que al desarrollador le interesa solo la estructura del valor, y lanecesidad de un tipo nombrado para esa estructura es de muy poca utilidad. Esto lleva a laintroducción de los tipos anónimos, que permiten definir nuevas estructuras “en línea” con suinicialización.

En C#, la sintaxis para los tipos anónimos es similar a la sintaxis de inicialización de objetos, con laexcepción de que el nombre del tipo es omitido. Por ejemplo, observe las dos siguientessentencias:

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 trespropiedades públicas Name, Age y CanCode. Las variables se diferencian en que v2 hacereferencia a una instancia de un tipo anónimo. En términos del CLR, los tipos anónimos no sondiferentes de cualquier otro tipo. Lo que hace especiales a los tipos anónimos es que no tienen unnombre significativo en el lenguaje de programación – la única manera de crear instancias de untipo anónimo es utilizando la sintaxis mostrada anteriormente.

Para permitir que las variables hagan referencia a instancias de tipos anónimos y al mismo tiempose beneficien del tipado estático, C# introduce la palabra clave var, que puede ser utilizada enlugar del nombre del tipo en las declaraciones de variables locales. Por ejemplo, observe elsiguiente 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 deltipo estático de la expresión utilizada para inicializar la variable. En el ejemplo, los tipos de s, n yb 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 tipostienen nombres significativos, pero una necesidad en el caso de variables que hacen referencia ainstancias 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 alsiguiente seudo-C#:

internal class ??? { string _Name; int _Age; bool _CanCode;

14/12/2010 El proyecto LINQ

msdn.microsoft.com/…/bb308959.aspx 10/24

Page 11: 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; sinembargo, el compilador garantiza que existirá a lo sumo un tipo anónimo para cada secuenciadiferente de pares nombre/tipo de propiedad dentro de cada ensamblado.

Debido a que los tipos anónimos se utilizan frecuentemente en proyecciones para seleccionar unoo más miembros de un valor estructurado existente, podemos simplemente hacer referencia a loscampos o propiedades de otro valor en la inicialización de un tipo anónimo. Esto resulta en que alnuevo tipo se le asocia una propiedad cuyo nombre, tipo y valor son copiados del campo opropiedad referenciada.

Por ejemplo, observe este ejemplo, que crea un nuevo valor estructurado combinando laspropiedades 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 == 51string wn = couple.Wife.Name; // wn == "Jane"

Las referencias a campos o propiedades mostradas anteriormente son simplemente una sintaxisconveniente 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 Ageobtenidas de bob y jane.Los tipos anónimos se utilizan con mayor frecuencia en la cláusula selectde 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

14/12/2010 El proyecto LINQ

msdn.microsoft.com/…/bb308959.aspx 11/24

Page 12: El proyecto LINQ

coincide con la forma que necesitamos para nuestro tratamiento, manteniendo las ventajas deltipado 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 deoperadores que ofrecen maneras útiles de manipular secuencias y componer consultas, dando alusuario un alto nivel de control sobre el resultado dentro del marco de trabajo conveniente de losoperadores 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 devalores 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 sonproducidos, se han definido operadores de consulta estándar para controlar ese orden. El másbásico de estos operadores es el operador OrderBy.

Los operadores OrderBy y OrderByDescending pueden ser aplicados a cualquier fuente deinformación y permitir al usuario suministrar una función de extracción de clave que produce elvalor utilizado para ordenar los resultados. OrderBy y OrderByDescending también aceptan unafunción de comparación opcional que puede ser utilizada para imponer un orden parcial sobre lasclaves. Veamos un ejemplo básico:

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

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

// ordenación por longitudvar 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ónde los miembros de la fuente en base a la comparación de cadenas. Las segundas dos consultasproducen nuevas secuencias basadas en la ordenación de los miembros de la fuente según lalongitud de cada cadena.

Para permitir múltiples criterios de ordenación, tanto OrderBy como OrderByDescending devuelvenSortedSequence<T> en vez de la interfaz genérica IEnumerable<T>. Solo dos operadores estándefinidos sobre el tipo SortedSequence<T>: ThenBy y ThenByDescending, que aplican criterios deordenación adicionales (subordinados). Los propios ThenBy/ThenByDescending devuelvenSortedSequence<T>, permitiendo la utilización de cualquier cantidad de operadoresThenBy/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 siguientesecuencia de valores:

"Burke", "David", "Frank", "Albert", "Connor", "George", "Harris",

14/12/2010 El proyecto LINQ

msdn.microsoft.com/…/bb308959.aspx 12/24

Page 13: El proyecto LINQ

"Everett"

Además de la familia de operadores OrderBy, entre los operadores de consulta estándar tambiénse incluye el operador Reverse. Reverse simplemente enumera una secuencia y produce losmismos valores en orden inverso. A diferencia de OrderBy, Reverse no tiene en cuenta los propiosvalores para determinar el orden, sino que se apoya únicamente en el orden en que los valoresson producidos por la fuente subyacente.El operador OrderBy impone una ordenación sobre unasecuencia de valores. Entre los operadores de consulta estándar también se incluye el operadorGroupBy, que impone la partición en grupos de los valores de una secuencia en base a unafunción de extracción de clave. El operador GroupBy devuelve una secuencia de valoresIGrouping, uno para cada valor de clave distinto encontrado. Un IGrouping es un IEnumerable queadicionalmente 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 longitudvar 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 HarrisStrings of length 5 Burke David FrankStrings 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 longitudvar 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

14/12/2010 El proyecto LINQ

msdn.microsoft.com/…/bb308959.aspx 13/24

Page 14: El proyecto LINQ

A C G HStrings of length 5 B D FStrings of length 7 E

Observe en este ejemplo que el tipo proyectado no tiene que ser el mismo tipo original. En estecaso, hemos creado una agrupación de enteros a caracteres a partir de una secuencia decadenas.

Principio de la página

Select vs. SelectMany

Varios operadores de consulta estándar se han definido para permitir la acumulación de unasecuencia 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 unvalor acumulado (cuyo valor inicial viene dado por el parámetro seed de Aggregate). El resultadode la evaluación de la expresión lambda sustituye al valor acumulado anterior, y Aggregatedevuelve el resultado final de la expresión lambda.

Por ejemplo, el siguiente programa utiliza Aggregate para acumular la cantidad general decaracteres 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ándartambié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. Lasfunciones 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 unafunció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

14/12/2010 El proyecto LINQ

msdn.microsoft.com/…/bb308959.aspx 14/24

Page 15: 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 lasecuencia original. Si su función de transformación devuelve un valor que es en sí mismo unasecuencia, es responsabilidad del consumidor recorrer manualmente las sub-secuencias. Porejemplo, observe el siguiente programa, que descompone cadenas en palabras utilizando elmé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 utilizarel operador SelectMany en vez del operador Select. El operador SelectMany funciona de unamanera similar al operador Select. Se diferencia en que espera que la función de transformacióndevuelva una secuencia, que será entonces expandida por el operador SelectMany. Este esnuestro 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 partede 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 deinformación diferente, pero tiene dentro de su ámbito al parámetro n pasado de la fuente másexterna. Por lo tanto, people.Where es llamado una vez para cada n, con las secuenciasresultantes aplanadas por SelectMany para el resultado final. El resultado es una secuencia detodas las personas cuyo nombre aparece en el array names.

Principio de la página

14/12/2010 El proyecto LINQ

msdn.microsoft.com/…/bb308959.aspx 15/24

Page 16: El proyecto LINQ

Operadores de encuentro

En un programa orientado a objetos, los objetos relacionados con otros están generalmenteenlazados mediante referencias a objetos fáciles de navegar. Esto generalmente no se cumplepara fuentes de información externas, donde los registros de datos frecuentemente no tienen otraopción que “apuntar” a otros de manera simbólica, mediante claves externas u otros datos quepermitan identificar unívocamente a la entidad apuntada. El concepto de encuentros se refiere ala 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 decadenas con personas cuyos nombres son esas cadenas. Sin embargo, para este propósitoespecífico, el enfoque basado en SelectMany no es muy eficiente – recorrerá todos los elementosde 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 dehacer 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 esaplicado 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 delos elementos de las secuencias externa e interna, respectivamente. Estas claves son las que elmétodo Join utiliza para buscar las coincidencias de elementos. Aquí queremos que los propiosnombres coincidan con la propiedad Name de las personas. La expresión lambda final es entoncesresponsable de producir los elementos de la secuencia resultante: es llamada para cada pareja deelementos coincidentes n y p, y es utilizada para dar forma al resultado. En este caso, hemoselegido descartar n y devolver p. El resultado final es la lista de elementos Person de people cuyoName está en la lista names.Un pariente más potente de Join es el operador GroupJoin. GroupJoin se diferencia de Join en elmodo en que se utiliza la expresión lambda que da forma al resultado: en vez de ser invocada paracada pareja individual de elementos externo e interno, será llamada solamente una vez para cadaelemento externo, con una secuencia de todos los elementos internos que coinciden con eseelemento 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 depersonas que tiene ese nombre. Por lo tanto, el operador GroupJoin permite basar los resultadosen 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 delas interfaces IEnumerable/IEnumerator de .NET Framework. La sentencia foreach esestrictamente opcional, pero es un mecanismo del lenguaje que ha demostrado ser muyconveniente y popular.

14/12/2010 El proyecto LINQ

msdn.microsoft.com/…/bb308959.aspx 16/24

Page 17: El proyecto LINQ

Apoyándose en este precedente, la sintaxis de consultas simplifica las expresiones de consultacon una sintaxis declarativa para los operadores de consulta más comunes: Where, Join,GroupJoin, Select, SelectMany, GroupBy, OrderBy, ThenBy, OrderByDescending yThenByDescending.

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áscompactas y fáciles de leer, pero completamente opcionales. Toda expresión que puede serescrita mediante sintaxis de consultas tiene una sintaxis correspondiente (más verbosa) utilizandola notación de punto.

Comencemos analizando la estructura básica de una expresión de consulta. Cada expresión deconsulta en C# comienza con una cláusula from y termina con una cláusula select o group. Laclá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. Cadacláusula from es un generador que introduce una variable de iteración sobre una secuencia, cadacláusula let da nombre al resultado de una expresión, y cada cláusula where es un filtro queexcluye elementos del resultado. Cada cláusula join combina una nueva fuente de datos con losresultados de un from o join anterior. La cláusula final select o group puede ir precedida de unaclá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

14/12/2010 El proyecto LINQ

msdn.microsoft.com/…/bb308959.aspx 17/24

Page 18: 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 lasiguiente 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 connombres específicos. La implementación exacta de los operadores de consulta que es elegida, porlo tanto, depende tanto del tipo de las variables consultadas como de los métodos extensores queestán en ámbito.

Las expresiones de consulta mostradas hasta ahora han utilizado un único generador. Cuando seutiliza más de un generador, cada generador subsiguiente es evaluado en el contexto de supredecesor. 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 BurkeFrank FrankDavid 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

14/12/2010 El proyecto LINQ

msdn.microsoft.com/…/bb308959.aspx 18/24

Page 19: El proyecto LINQ

en el resultado externo.

Una clase especial de generador es la cláusula join, que introduce los elementos provenientes deotra fuente que coinciden según las claves indicadas con los elementos de la cláusula from o joinprecedente. Una cláusula join puede producir los elementos coincidentes uno a uno, pero si esespecificada 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 unaque 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 consultasubsiguiente. Para dar soporte a esto, las expresiones de consulta utilizan la palabra clave intopara 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 unaclá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 EverettStrings of length 6 Albert Connor George HarrisStrings of length 5 Burke David Frank

Esta sección ha descrito cómo C# implementa las expresiones de consulta. Otros lenguajespueden elegir ofrecer sintaxis explícita para operadores de consulta adicionales, o simplemente noofrecer las expresiones de consulta.

Es importante señalar que la sintaxis de consultas no está “cableada” de ninguna manera a losoperadores de consulta estándar. Es una característica puramente sintáctica que puede aplicarsea cualquier cosa que satisfaga el patrón LINQ implementando los métodos subyacentes con losnombres y firmas apropiados. Los operadores de consulta estándar descritos anteriormente hacenesto utilizando métodos extensores para aumentar la interfaz IEnumerable<T>. Los desarrolladorespueden utilizar la sintaxis de consultas sobre cualquier tipo que deseen, siempre que se asegurende que éste se adhiere al patrón LINQ, ya sea mediante implementación directa de los métodos

14/12/2010 El proyecto LINQ

msdn.microsoft.com/…/bb308959.aspx 19/24

Page 20: 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 APIbasadas 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 lassiguientes secciones.

Principio de la página

DLinq: Integración de SQL

Las consultas integradas en los lenguajes .NET pueden ser utilizadas para consultar almacenes dedatos relacionales sin abandonar la sintaxis o el entorno de tiempo de compilación del lenguaje deprogramación local. Esta facilidad, llamada DLinq, se aprovecha de la integración de la informaciónde esquemas SQL en los metadatos del CLR. Esta integración compila las definiciones de tablas yvistas 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 delCLR corresponden a datos SQL externos. El atributo [Table] puede ser aplicado a una clase yasocia el tipo del CLR con una tabla o vista nombrada de SQL. El atributo [Column] puede seraplicado 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 deSQL. 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

14/12/2010 El proyecto LINQ

msdn.microsoft.com/…/bb308959.aspx 20/24

Page 21: El proyecto LINQ

anulables del CLR (los tipos anulables aparecieron en la versión 2 de .NET Framework), y que paralos 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ónLINQ traduce la consulta de su árbol de expresión a una expresión SQL y un objeto DbCommandde ADO.NET adecuado para la evaluación remota. Por ejemplo, observe esta simple consulta:

// establecer contexto de consulta sobre una conexión de ADO.NETDataContext 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 CLRTable<Person> custs = context.GetTable<Person>();Table<Order> orders = context.GetTable<Order>();

// construir la consultavar 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 consultaforeach (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 deconsulta estándar a SQL. DataContext utiliza un objeto de ADO.NET IDbConnection existente paraacceder al almacén, y puede ser inicializado bien mediante un objeto que represente a unaconexió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 enexpresiones de consulta para representar la tabla o vista remota. Las llamadas a GetTable nocausan ninguna interacción con la base de datos – en vez de eso, representan el potencial deinteractuar con la tabla o vista remota utilizando expresiones de consulta. En nuestro ejemploanterior, la consulta no es transmitida al almacén de datos hasta que el programa itera sobre laexpresión de consulta, en este caso utilizando la sentencia foreach de C#. Cuando el programaitera por primera vez sobre la consulta, la maquinaria del DataContext traduce el árbol deexpresió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 lenguajede programación local, los desarrolladores obtienen toda la potencia del modelo relacional sintener que “cocer” estáticamente las relaciones dentro del tipo del CLR. Dicho esto, un mapeadoobjeto/relacional completo podría también aprovechar esta capacidad básica de consulta paraaquellos usuarios que deseen tal funcionalidad. DLinq ofrece una funcionalidad de mapeadoobjeto/relacional con la que el desarrollador puede definir y navegar por las relaciones entreobjetos. 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 paracombinar ambas. Los ficheros de mapeado externos permiten separar el mapeado del modelo deobjetos para obtener unas posibilidades más amplias de mapeado.

Principio de la página

14/12/2010 El proyecto LINQ

msdn.microsoft.com/…/bb308959.aspx 21/24

Page 22: El proyecto LINQ

XLinq: Integración de XML

Las consultas integradas en los lenguajes .NET para XML (XLinq) permiten que los datos XML seanconsultados utilizando los operadores de consulta estándar, así como mediante operadoresespecíficos de árboles que ofrecen posibilidades de navegación por los descendientes, ancestros yhermanos al estilo de XPath. Ofrecen una representación eficiente en memoria para XML que seintegra con la infraestructura de lectura/escritura de System.Xml existente y es más fácil de uasrque el DOM de W3C. Tres tipos se encargan de la mayor parte del trabajo de integración de XMLcon las consultas: XName, XElement y XAttribute.

XName suministra una manera sencilla de trabajar con los identificadores cualificados medianteespacios 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 quese 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 losdesarrolladores 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, yque la implementación de ToString produce el XML textual. Los elementos XML también puedenser 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 escribirconsultas contra información no basada en XML y producir resultados XML construyendo objetosXElement 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 seconstruyan a partir del resultado de esta clase de consultas, el constructor de XElement permiteque se le pase directamente como argumento una secuencia de elementos:

var x = new XElement("People", from p in people where p.CanCode

14/12/2010 El proyecto LINQ

msdn.microsoft.com/…/bb308959.aspx 22/24

Page 23: 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.0también soporta el uso de literales XML, que permiten que las expresiones de consulta seanexpresadas utilizando una sintaxis XML declarativa directamente dentro de Visual Basic. El ejemploanterior 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 utilizandoconsultas integradas en los lenguajes. Los tipos XElement y XAttribute también simplifican laextracción de información de estructuras XML. XElement ofrece métodos de acceso que permitenaplicar expresiones de consulta a los ejes tradicionales de XPath. Por ejemplo, la siguienteconsulta 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 inicializadoren 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 elvalor textual como un tipo primitivo. Para gestionar los datos ausentes, podemos simplementeconvertir 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 accedidosutilizando una sintaxis más compacta y directa llamada propiedades de los ejes XML. Podemosutilizar esta funcionalidad para escribir la sentencia C# anterior de la siguiente forma:

14/12/2010 El proyecto LINQ

msdn.microsoft.com/…/bb308959.aspx 23/24

Page 24: El proyecto LINQ

Dim persons = _From e In x...<Person> _ Select new Person { _ .Name = e.Value, _ .Age = [email protected] ?? 21 _ }

En Visual Basic, x...<Person> recupera todos los elementos en la colección Descendants de x quetienen el nombre Person, mientras que la expresión e.@Age encuentra todos los XAttributes con elnombre Age. La propiedad Value obtiene el primer atributo de la colección y llama a la propiedadValue 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 loslenguajes 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ónde claves puedan utilizarse como código ejecutable opaco o como datos transparentes enmemoria adecuados para su tratamiento posterior o traducción. Los operadores de consultaestándar definidos por el Proyecto LINQ operan sobre cualquier fuente de información basada enIEnumerable<T>, y se integran con ADO.NET (DLinq) y System.Xml (XLinq) para permitir que losdatos relacionales y XML obtengan los beneficios de las consultas integradas en los lenguajes.

Principio de la página

14/12/2010 El proyecto LINQ

msdn.microsoft.com/…/bb308959.aspx 24/24