Introducción a las consultas LINQ (C#)

Una consulta es una expresión que recupera datos de un origen de datos. Las consultas se suelen expresar en un lenguaje de consultas especializado. Con el tiempo se han desarrollado diferentes lenguajes para los distintos tipos de orígenes de datos, como SQL para las bases de datos relacionales y XQuery para XML. Por lo tanto, los programadores han tenido que aprender un lenguaje de consultas nuevo para cada tipo de origen de datos o formato de datos que deben admitir. LINQ simplifica esta situación al ofrecer un modelo coherente para trabajar con los datos de varios formatos y orígenes. En una consulta LINQ siempre se trabaja con objetos. Se usan los mismos patrones de codificación básicos para consultar y transformar datos de documentos XML, bases de datos SQL, conjuntos de datos de ADO.NET, colecciones de .NET y cualquier otro formato para el que haya disponible un proveedor de LINQ.

Las tres partes de una operación de consulta

Todas las operaciones de consulta LINQ constan de tres acciones distintas:

  1. Obtener el origen de datos.

  2. Crear la consulta.

  3. Ejecutar la consulta.

En el siguiente ejemplo se muestra cómo se expresan las tres partes de una operación de consulta en código fuente. En el ejemplo se usa una matriz de enteros como origen de datos para su comodidad, aunque se aplican los mismos conceptos a otros orígenes de datos. En el resto de este tema se hará 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 toda la operación de consulta. En LINQ, la ejecución de la consulta es distinta de la propia consulta. En otras palabras, no ha recuperado ningún dato simplemente creando una variable de consulta.

Diagrama de la operación de consulta LINQ completa.

El origen de datos

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

Un tipo consultable no requiere ninguna modificación ni ningún tratamiento especial para actuar como origen de datos de LINQ. Si el origen de datos no está en la memoria como tipo consultable, el proveedor de LINQ debe representarlo como tal. Por ejemplo, LINQ to XML carga un documento XML en un tipo consultable XElement:

// 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 las herramientas de LINQ to SQL en Visual Studio. 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, Customers representa una tabla específica en una base de datos, y el tipo del resultado de la consulta, IQueryable<T>, se deriva de IEnumerable<T>.

Northwnd db = new Northwnd(@"c:\northwnd.mdf");

// Query for customers in London.
IQueryable<Customer> custQuery =
    from cust in db.Customers
    where cust.City == "London"
    select cust;

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

Nota

Los tipos como ArrayList, que admiten la interfaz no genérica IEnumerable, también se pueden usar como origen de datos de LINQ. Para más información, consulte el procedimiento para consultar un objeto ArrayList con LINQ (C#).

La consulta

La consulta especifica la información que se debe recuperar de los orígenes de datos. Opcionalmente, una consulta también especifica cómo se debe ordenar, agrupar y conformar esa información antes de que se devuelva. Las consultas se almacenan en una variable de consulta y se inicializan con una expresión de consulta. Para facilitar la escritura de consultas, C# ha incorporado una 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. Estas y otras cláusulas de consulta se tratan con detalle en la sección Language Integrated Query (LINQ). Por ahora, lo importante es que en LINQ la variable de consulta no efectúa ninguna acción y no devuelve ningún dato. Lo único que hace es almacenar la información necesaria para generar los resultados cuando se ejecuta la consulta en algún momento posterior. 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 (C#).

Nota

Las consultas también se pueden expresar empleando una sintaxis de método. Para obtener más información, vea Query Syntax and Method Syntax in LINQ (Sintaxis de consulta y sintaxis de método en LINQ).

Ejecución de la consulta

Ejecución aplazada

Como se ha indicado anteriormente, la variable de consulta solo almacena los comandos de 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 aplazada 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, puede ejecutarla tantas veces como desee. Por ejemplo, se puede tener una base de datos que esté siendo actualizada de forma continua por otra aplicación. En su aplicación, se puede crear una consulta que recupere los datos más recientes y se puede ejecutar repetidamente de acuerdo con un intervalo para recuperar resultados diferentes cada vez.

Forzar la ejecución inmediata

Las consultas que llevan a cabo funciones de agregación en un intervalo de elementos de origen primero deben recorrer en iteración dichos elementos. Ejemplos de estas consultas son Count, Max, Average y First. Se ejecutan sin una instrucción foreach explícita, ya que la propia consulta debe usar foreach para poder devolver un resultado. Tenga en cuenta también que estos tipos de consultas devuelven un único valor, y 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 caché los resultados correspondientes, puede llamar a los métodos ToList o ToArray.

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 colocando el bucle foreach justo después de la expresión de consulta, aunque, si se llama a ToList o a ToArray, también se almacenan en caché todos los datos de un objeto de colección.

Vea también