Ejecución de la consulta

Una vez creada por un usuario una consulta LINQ, se convierte en un árbol de comandos. Un árbol de comandos es una representación de una consulta que es compatible con Entity Framework. Posteriormente, el árbol de comandos se ejecuta en el origen de datos. En el momento de la ejecución de la consulta, se evalúan todas las expresiones de consulta (es decir, todos los componentes de la consulta), incluidas las expresiones que se utilizan en la materialización del resultado.

El momento en que se ejecutan las expresiones de consulta puede variar. Las consultas LINQ siempre se ejecutan cuando se recorre en iteración la variable de consulta, no cuando se crea la citada variable de consulta. Esto se denomina ejecución aplazada. También se puede obligar a que la consulta se ejecute inmediatamente, lo que es útil para almacenar en caché los resultados de la consulta. Esto se describe más adelante en este tema.

Cuando se ejecuta una consulta de LINQ to Entities, algunas de sus expresiones pueden ejecutarse en el servidor y ciertas partes pueden ejecutarse de forma local en el cliente. La evaluación en el cliente de una expresión se lleva a cabo antes de ejecutar la consulta en el servidor. Si una expresión se evalúa en el cliente, el resultado de esa evaluación se sustituye por la expresión en la consulta, y ésta se ejecuta entonces en el servidor. Dado que las consultas se ejecutan en el origen de datos, la configuración de este invalida el comportamiento especificado en el cliente. Por ejemplo, la precisión numérica y el tratamiento de los valores NULL dependen de la configuración de servidor. Cualquier excepción que se produzca durante la ejecución de la consulta en el servidor se pasa directamente al cliente.

Ejecución de consultas en diferido

En una consulta que devuelve una secuencia de valores, la variable de consulta por sí misma nunca conserva los resultados de la consulta y solo almacena los comandos de la misma. La ejecución de la consulta se aplaza hasta que la variable de consulta se recorre en iteración en un bucle foreach o For Each. Esto se denomina ejecución aplazada; es decir, la ejecución de la consulta se produce después de que se cree la consulta. Esto significa que se puede ejecutar una consulta con la frecuencia que se desee. Esto es útil cuando, por ejemplo, se tiene una base de datos que otras aplicaciones están actualizando. En su aplicación puede crear una consulta para recuperar la información más reciente y ejecutar de forma repetida la consulta, devolviendo cada vez la información actualizada.

La ejecución aplazada permite combinar varias consultas o ampliar una consulta. Cuando se amplía una consulta, se modifica para incluir las nuevas operaciones. La ejecución eventual reflejará los cambios. En el siguiente ejemplo, la primera consulta devuelve todos los productos. La segunda consulta amplía la primera usando Where para devolver todos los productos de tamaño "L":

Using context As New AdventureWorksEntities()
    Dim productsQuery = _
        From p In context.Products _
        Select p

    Dim largeProducts = _
        productsQuery.Where(Function(p) p.Size = "L")

    Console.WriteLine("Products of size 'L':")
    For Each product In largeProducts
        Console.WriteLine(product.Name)
    Next
End Using
using (AdventureWorksEntities context = new AdventureWorksEntities())
{
    IQueryable<Product> productsQuery =
        from p in context.Products
        select p;

    IQueryable<Product> largeProducts = productsQuery.Where(p => p.Size == "L");

    Console.WriteLine("Products of size 'L':");
    foreach (var product in largeProducts)
    {
        Console.WriteLine(product.Name);
    }
}

Una vez ejecutada una consulta, todas las consultas sucesivas utilizarán los operadores de LINQ en memoria. Si se recorre en iteración la variable de la consulta utilizando una instrucción foreach o For Each o llamando a uno de los operadores de conversión de LINQ, se producirá la ejecución inmediata. Entre estos operadores de conversión se incluyen los siguientes: ToList, ToArray, ToLookup y ToDictionary.

Ejecución de consultas inmediata

A diferencia de la ejecución aplazada de consultas que producen una secuencia de valores, las consultas que devuelven un valor singleton se ejecutan inmediatamente. Algunos ejemplos de consultas singleton son Average, Count, First y Max. Se ejecutan inmediatamente porque la consulta debe producir una secuencia para calcular el resultado singleton. También se puede forzar la ejecución inmediata. Esto es útil cuando se desea almacenar en memoria caché los resultados de una consulta. Para forzar la ejecución inmediata de una consulta que no produce un valor singleton, se puede llamar a los métodos ToList, ToDictionary o ToArray en una consulta o una variable de consulta. En el ejemplo siguiente se utiliza el método ToArray para evaluar de forma inmediata una secuencia en una matriz.

Using context As New AdventureWorksEntities
    Dim products As ObjectSet(Of Product) = context.Products

    Dim prodArray As Product() = ( _
        From product In products _
        Order By product.ListPrice Descending _
        Select product).ToArray()

    Console.WriteLine("The list price from highest to lowest:")
    For Each prod As Product In prodArray
        Console.WriteLine(prod.ListPrice)
    Next
End Using
using (AdventureWorksEntities context = new AdventureWorksEntities())
{
    ObjectSet<Product> products = context.Products;

    Product[] prodArray = (
        from product in products
        orderby product.ListPrice descending
        select product).ToArray();

    Console.WriteLine("Every price from highest to lowest:");
    foreach (Product product in prodArray)
    {
        Console.WriteLine(product.ListPrice);
    }
}

También se puede forzar la ejecución colocando el bucle foreach o For Each inmediatamente después de la expresión de consulta, pero si se llama a los métodos ToList o ToArray, se almacenarán en caché todos los datos de un solo objeto de la colección.

Ejecución en el almacén

En general, las expresiones de LINQ to Entities se evalúan en el servidor, y no es de esperar que el comportamiento de la expresión siga la semántica de Common Language Runtime (CLR), sino la del origen. Sin embargo, hay excepciones, como cuando la expresión se ejecuta en el cliente. Esto puede producir resultados inesperados, por ejemplo cuando el servidor y el cliente están en zonas horarias diferentes.

Algunas expresiones de la consulta se pueden ejecutar en el cliente. En general, se espera que la mayor parte de la ejecución de la consulta se produzca en el servidor. Además de los métodos ejecutados en elementos de consulta asignados al origen de datos, suele haber expresiones de la consulta que se pueden ejecutar localmente. La ejecución local de una expresión de consulta produce un valor que se puede utilizar en la ejecución de la consulta o en la generación del resultado.

Ciertas operaciones se ejecutan siempre en el cliente, como el enlace de valores, subexpresiones, subconsultas de cierres, y la materialización de objetos en los resultados de la consulta. La consecuencia final es que estos elementos (por ejemplo, los valores de parámetro) no se pueden actualizar durante la ejecución. Los tipos anónimos se pueden crear alineados en el origen de datos, pero no se debe suponer que esto se vaya a producir. Las agrupaciones alineadas también se pueden crear en el almacén, pero no se debe suponer que esto tenga lugar en cada instancia. En general, es preferible no hacer suposiciones sobre lo que se crea en el servidor.

En esta sección se describen los escenarios en que el código se ejecuta localmente en el cliente. Para obtener más información sobre qué tipos de expresiones se ejecutan localmente, vea Expresiones en consultas de LINQ to Entities.

Literales y parámetros

Las variables locales, como la variable orderID del ejemplo siguiente, se evalúan en el cliente.

Dim orderID As Integer = 51987

Dim salesInfo = _
    From s In context.SalesOrderHeaders _
    Where s.SalesOrderID = orderID _
    Select s
int orderID = 51987;

IQueryable<SalesOrderHeader> salesInfo =
    from s in context.SalesOrderHeaders
    where s.SalesOrderID == orderID
    select s;

Los parámetros de métodos también se evalúan en el cliente. El parámetro orderID que se pasa al método MethodParameterExample, como se puede apreciar más abajo, es un ejemplo.

Function MethodParameterExample(ByVal orderID As Integer)
    Using context As New AdventureWorksEntities()

        Dim salesInfo = _
            From s In context.SalesOrderHeaders _
            Where s.SalesOrderID = orderID _
            Select s

        Console.WriteLine("Sales order info:")
        For Each sale As SalesOrderHeader In salesInfo
            Console.WriteLine("OrderID: {0}, Total due: {1}", sale.SalesOrderID, sale.TotalDue)
        Next
    End Using

End Function
public static void MethodParameterExample(int orderID)
{
    using (AdventureWorksEntities context = new AdventureWorksEntities())
    {
        
        IQueryable<SalesOrderHeader> salesInfo =
            from s in context.SalesOrderHeaders
            where s.SalesOrderID == orderID
            select s;                

        foreach (SalesOrderHeader sale in salesInfo)
        {
            Console.WriteLine("OrderID: {0}, Total due: {1}", sale.SalesOrderID, sale.TotalDue);
        }
    }
}

Convertir literales en el cliente

La conversión de null a un tipo CLR se ejecuta en el cliente:

Dim query = _
    From c In context.Contacts _
    Where c.EmailAddress = CType(Nothing, String) _
    Select c
IQueryable<Contact> query =
    from c in context.Contacts
    where c.EmailAddress == (string)null
    select c;

La conversión a un tipo, como un Decimal que acepta valores NULL, se ejecuta en el cliente:

Dim weight = CType(23.77, Decimal?)
Dim query = _
    From product In context.Products _
        Where product.Weight = weight _
        Select product
var weight = (decimal?)23.77;
IQueryable<Product> query =
    from product in context.Products
    where product.Weight == weight
    select product;

Constructores para literales

Los nuevos tipos CLR que se pueden asignar a tipos del modelo conceptual se ejecutan en el cliente:

Dim weight = New Decimal(23.77)
Dim query = _
    From product In context.Products _
    Where product.Weight = weight _
    Select product
var weight = new decimal(23.77);
IQueryable<Product> query =
    from product in context.Products
    where product.Weight == weight
    select product;

Las nuevas matrices también se ejecutan en el cliente.

Excepciones en el almacén

Los errores en el almacén que tienen lugar durante la ejecución de la consulta se pasan al cliente y no se asignan ni controlan.

Configuración del almacén

Cuando la consulta se ejecuta en el almacén, la configuración del almacén invalida todos los comportamientos del cliente, y la semántica del almacén se expresa para todas las operaciones y expresiones. El resultado puede ser una diferencia de comportamiento entre la ejecución en el CLR y la ejecución en el almacén en áreas como las comparaciones de NULL, la ordenación de GUID, la precisión y la exactitud de las operaciones que afectan a tipos de datos no precisos (como los tipos de punto flotante o DateTime) y las operaciones de cadena. Es importante tener esto en cuenta al examinar los resultados de la consulta.

Por ejemplo, a continuación se indican algunas diferencias de comportamiento entre CLR y SQL Server:

  • SQL Server ordena los GUID de manera diferente que CLR.

  • También puede haber diferencias en la precisión del resultado cuando se trabaja con el tipo Decimal en SQL Server. Esto se debe a los requisitos de precisión fijados para el tipo decimal de SQL Server. Por ejemplo, el valor medio de los valores Decimal 0.0, 0.0, y 1.0 es 0.3333333333333333333333333333 en memoria en el cliente, pero 0.333333 en el almacén (si se toma como base la precisión predeterminada para el tipo decimal de SQL Server).

  • Algunas operaciones de comparación de cadenas también se tratan en SQL Server de forma diferente que en CLR. El comportamiento de la comparación de cadenas depende de los valores de intercalación en el servidor.

  • Las llamadas a funciones o métodos, cuando se incluyen en una consulta de LINQ to Entities, se asignan a funciones canónicas en Entity Framework, que, posteriormente, se convierten a Transact-SQL y se ejecutan en la base de datos de SQL Server. Hay casos en que el comportamiento que muestran estas funciones asignadas puede diferir de la implementación en las bibliotecas de clases base. Por ejemplo, una llamada a los métodos Contains, EndsWith y StartsWith con una cadena vacía como parámetro, devolverá true si se ejecuta en CLR pero devolverá false si se ejecuta en SQL Server. El método EndsWith también puede devolver resultados diferentes porque SQL Server considera que dos cadenas son iguales si solo se diferencian en el espacio en blanco final, mientras que CLR considera que no son iguales. Esto se muestra en el ejemplo siguiente:

Using context As New AdventureWorksEntities()

    Dim query = _
        From p In context.Products _
        Where p.Name = "Reflector" _
        Select p.Name

    Dim q = _
        query.Select(Function(c) c.EndsWith("Reflector "))

    Console.WriteLine("LINQ to Entities returns: " & q.First())
    Console.WriteLine("CLR returns: " & "Reflector".EndsWith("Reflector "))
End Using
using (AdventureWorksEntities context = new AdventureWorksEntities())
{
    IQueryable<string> query = from p in context.Products
                               where p.Name == "Reflector"
                               select p.Name;

    IEnumerable<bool> q = query.Select(c => c.EndsWith("Reflector "));

    Console.WriteLine("LINQ to Entities returns: " + q.First());
    Console.WriteLine("CLR returns: " + "Reflector".EndsWith("Reflector "));

}