¿Le resultó útil esta página?
Sus comentarios sobre este contenido son muy importantes. Háganos saber su opinión.
¿Tiene comentarios adicionales?
Caracteres restantes: 1500
Exportar (0) Imprimir
Expandir todo
Expandir Minimizar

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

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

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.

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.

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

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

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

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

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”.

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

// 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).

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.

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

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;

  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 coincide con la forma que necesitamos para nuestro tratamiento, manteniendo las ventajas del tipado estático.

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.

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", 
"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
  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.

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
int total2 = names.Sum(s => s.Length); // total2 == 46

Observe que la segunda sentencia Sum es equivalente al ejemplo anterior que utilizaba Aggregate.

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.

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.

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.

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

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 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 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.

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 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.

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
                  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:

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.

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.

Mostrar:
© 2015 Microsoft