Introducción a consultas con LINQ

Actualización: noviembre 2007

Una consulta es una expresión que recupera datos de un origen de datos. Las consultas normalmente se expresan en un lenguaje de consultas especializado. A lo largo del tiempo se han ido desarrollando lenguajes diferentes para los distintos tipos de orígenes de datos, como SQL para las bases de datos relacionales y XQuery para XML. Por consiguiente, los programadores han tenido que aprender un nuevo lenguaje de consultas para cada tipo de formato u origen de datos que deben admitir. LINQ simplifica esta situación, proporcionando un modelo coherente para trabajar con datos de distintos tipos de formatos y orígenes de datos. En una consulta LINQ, siempre se trabaja con objetos. Se utilizan los mismos modelos de codificación básicos para consultar y transformar datos de documentos XML, bases de datos SQL, conjuntos de datos ADO.NET, colecciones .NET y cualquier otro formato para el que haya disponible un proveedor LINQ.

Las tres partes de una operación de consulta

Todas las operaciones de consulta LINQ se componen de tres acciones distintas:

  1. Obtención del origen de datos.

  2. Creación de la consulta.

  3. Ejecución de la consulta.

En el ejemplo siguiente se muestra cómo se expresan las tres partes de una operación de consulta en el código fuente. En el ejemplo se utiliza por comodidad una matriz de enteros como origen de datos, pero los mismos conceptos se aplican a otros orígenes de datos. En el resto del tema se hace referencia a este ejemplo.

class IntroToLINQ
{        
    static void Main()
    {
        // The Three Parts of a LINQ Query:
        //  1. Data source.
        int[] numbers = new int[7] { 0, 1, 2, 3, 4, 5, 6 };

        // 2. Query creation.
        // numQuery is an IEnumerable<int>
        var numQuery =
            from num in numbers
            where (num % 2) == 0
            select num;

        // 3. Query execution.
        foreach (int num in numQuery)
        {
            Console.Write("{0,1} ", num);
        }
    }
}

En la siguiente ilustración se muestra la operación de consulta completa. En LINQ, la ejecución de la consulta es distinta de la propia consulta; en otras palabras, no se recuperan datos con la simple creación de la variable de consulta.

Completar operación de consulta LINQ

El origen de datos

En el ejemplo anterior, como el origen de datos es una matriz, se admite implícitamente la interfaz genérica IEnumerable<T>. Este hecho implica que se puede consultar con LINQ. Una consulta se ejecuta en una instrucción foreach y foreach requiere IEnumerable o IEnumerable<T>. Los tipos que admiten IEnumerable<T> o una interfaz derivada, como la genérica IQueryable<T>, se conocen como tipos consultables.

Un tipo que se puede consultar no requiere ninguna modificación o tratamiento especial para servir como origen de datos LINQ. Si los datos de origen aún no están en memoria como tipo que se puede consultar, el proveedor LINQ debe representarlos como tales. Por ejemplo, LINQ to XML carga un documento XML en un tipo XElement que se puede consultar:

// Create a data source from an XML document.
// using System.Xml.Linq;
XElement contacts = XElement.Load(@"c:\myContactList.xml");

Con LINQ to SQL, primero se crea una asignación relacional de objetos en tiempo de diseño, ya sea manualmente o mediante el Diseñador relacional de objetos (Diseñador R/O). Después, se escriben las consultas en los objetos y, en tiempo de ejecución, LINQ to SQL controla la comunicación con la base de datos. En el ejemplo siguiente, Customer representa una tabla concreta de la base de datos y Table<Customer> admite la interfaz genérica IQueryable<T>, que se deriva de IEnumerable<T>.

// Create a data source from a SQL Server database.
// using System.Data.Linq;
DataContext db = new DataContext(@"c:\northwind\northwnd.mdf");

Para obtener más información sobre cómo crear tipos de orígenes de datos específicos, consulte la documentación de los distintos proveedores LINQ. Sin embargo, la regla básica es muy simple: un origen de datos LINQ es cualquier objeto que admite la interfaz genérica IEnumerable<T> o una interfaz que herede de ella.

Nota:

También se pueden utilizar tipos como ArrayList, que admite la interfaz no genérica IEnumerable, como origen de datos LINQ. Para obtener más información, vea Cómo: Consultar un objeto ArrayList con LINQ.

La consulta

La consulta especifica qué información se va a recuperar de uno o varios orígenes de datos. Opcionalmente, una consulta también especifica cómo debería ordenarse, agruparse y darse forma a esa información antes de ser devuelta. Una consulta se almacena en una variable de consulta y se inicializa con una expresión de consulta. Para simplificar la escritura de consultas, C# incluye nueva sintaxis de consulta.

La consulta del ejemplo anterior devuelve todos los números pares de la matriz de enteros. La expresión de consulta contiene tres cláusulas: from, where y select. (Si está familiarizado con SQL, habrá observado que el orden de las cláusulas se invierte respecto al orden de SQL.) La cláusula from especifica el origen de datos, la cláusula where aplica el filtro y la cláusula select especifica el tipo de los elementos devueltos. Ésta y las demás cláusulas de consulta se analizan con detalle en la sección Expresiones de consultas con LINQ (Guía de programación de C#). Aquí, lo importante es que, en LINQ, la propia variable de consulta no realiza ninguna acción ni devuelve datos. Simplemente almacena la información necesaria para generar los resultados cuando la consulta se ejecute posteriormente. Para obtener más información sobre cómo se construyen las consultas en segundo plano, vea Información general sobre operadores de consulta estándar.

Nota:

Las consultas también se pueden expresar con sintaxis de método. Para obtener más información, consulte Sintaxis de consultas y Sintaxis de métodos (LINQ).

Ejecución de la consulta

Ejecución diferida

Como se ha mencionado previamente, la variable de consulta sólo almacena los comandos de la consulta. La ejecución real de la consulta se aplaza hasta que se procese una iteración en la variable de consulta, en una instrucción foreach. Este concepto se conoce como ejecución diferida y se muestra en el ejemplo siguiente:

//  Query execution. 
foreach (int num in numQuery)
{
    Console.Write("{0,1} ", num);
}

La instrucción foreach es también donde se recuperan los resultados de la consulta. Por ejemplo, en la consulta anterior, la variable de iteración num contiene cada valor (de uno en uno) en la secuencia devuelta.

Dado que la propia variable de consulta nunca contiene los resultados de la consulta, se puede ejecutar tantas veces como se desee. Por ejemplo, puede que una aplicación independiente actualice continuamente una base de datos. En su aplicación, podría crear una consulta que recuperase los datos más recientes, y podría ejecutarla repetidamente cada cierto tiempo para recuperar cada vez resultados diferentes.

Forzar la ejecución inmediata

Las consultas que realizan funciones de agregación en un intervalo de elementos de origen primero deben recorrer en iteración dichos elementos. Algunos ejemplos de esas consultas son Count, Max, Average y First. Se ejecutan sin una instrucción foreach explícita porque la propia consulta debe utilizar foreach para devolver un resultado. Debe saber también que estos tipos de consultas devuelven un solo valor, no una colección IEnumerable. La consulta siguiente devuelve un recuento de los números pares de la matriz de origen:

var evenNumQuery = 
    from num in numbers
    where (num % 2) == 0
    select num;

int evenNumCount = evenNumQuery.Count();

Para forzar la ejecución inmediata de cualquier consulta y almacenar en memoria caché sus resultados, puede llamar al método ToList<TSource> o ToArray<TSource>.

List<int> numQuery2 =
    (from num in numbers
     where (num % 2) == 0
     select num).ToList();

// or like this:
// numQuery3 is still an int[]

var numQuery3 =
    (from num in numbers
     where (num % 2) == 0
     select num).ToArray();

También puede forzar la ejecución si coloca el bucle foreach justo después de la expresión de consulta. Sin embargo, al llamar a ToList o ToArray también se almacenan en memoria caché todos los datos en un objeto de colección único.

Vea también

Conceptos

Información general sobre el Diseñador relacional de objetos

Expresiones de consultas con LINQ (Guía de programación de C#)

Referencia

foreach, in (Referencia de C#)

Otros recursos

Introducción a LINQ en C#

Ejemplos de LINQ

Palabras clave de consultas (Referencia de C#)

LINQ and Deferred Execution Video