Execução da consulta

Depois de ser criada por um usuário, uma consulta LINQ é convertida em uma árvore de comando. Uma árvore de comando é uma representação de uma consulta compatível com o Entity Framework. Em seguida, a árvore de comando é executada na fonte de dados. Em tempo de execução de consulta, todas as expressões e consulta (isto é, todos os componentes da consulta) são avaliados, incluindo essas expressões usadas na materialização do resultado.

O ponto onde as expressões de consulta são executadas pode variar. As consultas LINQ são sempre executadas quando a variável de consulta é iterada sobre, não quando a variável de consulta é criada. Esse processo tem o nome de execução adiada. Você também pode forçar a execução imediata de uma consulta, o que é útil para armazenar os resultados da consulta em cache. Isso é descrito mais adiante neste tópico.

Quando uma consulta LINQ to Entities é executada, algumas expressões da consulta podem ser executadas no servidor e algumas partes podem ser executadas localmente no cliente. A avaliação de uma expressão do lado do cliente ocorre antes de a consulta ser executada no servidor. Se uma expressão for avaliada no cliente, o resultado da avaliação será substituído para a expressão na consulta, e a consulta será executada no servidor. Como as consultas são executadas na fonte de dados, a configuração da fonte de dados substitui o comportamento especificado no cliente. Por exemplo, manipulação de valor nulo e a precisão numérica dependem das configurações do servidor. Todas as exceções geradas durante a execução da consulta no servidor são passadas diretamente até o cliente.

Dica

Para obter um resumo útil dos operadores de consulta no formato de tabela, que permite identificar rapidamente o comportamento de execução de um operador, consulte Classificação de Operadores de Consulta Padrão por Forma de Execução (C#).

Execução de consulta adiada

Em uma consulta, que retorna uma sequência de valores, a variável de consulta nunca contém os resultados da consulta e armazena somente os comandos da consulta. A execução da consulta é adiada até que a variável da consulta seja iterada em um loop foreach ou For Each. Esse processo é conhecido como execução adiada, ou seja, a execução da consulta ocorre algum tempo depois que a consulta é construída. Isso significa que você pode executar uma consulta com a frequência que desejar. Isso é útil quando, por exemplo, você tem um banco de dados que está sendo atualizado por outros aplicativos. Em seu aplicativo, você pode criar uma consulta para recuperar as informações mais recentes e executar a consulta repetidamente, retornando informações atualizadas sempre.

A execução adiada permite que várias consultas sejam combinadas ou que uma consulta seja estendida. Quando é estendida, a consulta é modificada para incluir as novas operações, e a execução eventual refletirá as alterações. No exemplo a seguir, a primeira consulta retorna todos os produtos. A segunda consulta estende a primeira usando Where para retornar todos os produtos de tamanho “L”:

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

Depois que uma consulta foi executada, todas as consultas sucessivas usarão os operadores LINQ na memória. Iterar sobre a variável de consulta usando uma instrução foreach ou For Each ou chamando um dos operadores de conversão LINQ fará com que a execução seja imediata. Esses operadores de conversão incluem: ToList, ToArray, ToLookup e ToDictionary.

Execução imediata de consulta

Em comparação com a execução adiada de consultas que geram uma sequência de valores, as consultas que retornam um valor singleton são executadas imediatamente. Alguns exemplos de consultas singleton são Average, Count, First e Max. Essas consultas são executadas imediatamente porque a consulta deve gerar uma sequência para calcular o resultado singleton. Você também pode forçar a execução imediata. Isso é útil quando você deseja armazenar os resultados de uma consulta em cache. Para forçar a execução imediata de uma consulta que não produz um valor singleton, você pode chamar o método ToList, o método ToDictionary ou o método ToArray em uma consulta ou em uma variável de consulta. O exemplo a seguir usa o método ToArray para avaliar imediatamente uma sequência em uma matriz.

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

Você também pode forçar a execução colocando o loop foreach ou For Each imediatamente após a expressão da consulta, mas chamando ToList ou ToArray você armazena em cache todos os dados em um único objeto de coleção.

Execução de armazenamento

Em geral, as expressões no LINQ to Entities são avaliadas no servidor, e não se deve esperar que o comportamento da expressão siga a semântica do CLR (Common Language Runtime), mas sim a da fonte de dados. No entanto, existem exceções, como quando a expressão é executada no cliente. Isso pode provocar resultados inesperados, por exemplo, quando o servidor e o cliente estão em fusos horários diferentes.

Algumas expressões da consulta podem ser executadas no cliente. Em geral, espera-se que a maior parte da execução da consulta ocorra no servidor. Com exceção dos métodos executados em elementos de consulta mapeados para a fonte de dados, frequentemente há expressões na consulta que podem ser executadas localmente. A execução local de uma expressão de consulta produz um valor que pode ser usado na execução da consulta ou na construção do resultado.

Determinadas operações são sempre executadas no cliente, como a associação de valores, as subexpressões, as subconsultas de fechamentos e a materialização de objetos nos resultados da consulta. O efeito líquido disso é que esses elementos (por exemplo, valores de parâmetros) não podem ser atualizados durante a execução. Tipos anônimos também podem ser construídos embutidos na fonte de dados, mas não devem ser supostos a fazer isso. Os agrupamentos embutidos podem ser construídos na fonte de dados, também, mas isso não deve ser suposto em cada instância. Em geral, é melhor não fazer nenhuma suposição sobre o que é construído no servidor.

Esta seção descreve os cenários em que o código é executado localmente no cliente. Para obter mais informações sobre quais tipos de expressões são executados localmente, consulte Expressões em Consultas LINQ to Entities.

Literais e parâmetros

Variáveis locais, como a variável orderID no exemplo a seguir, são avaliadas no cliente.

int orderID = 51987;

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

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

Os parâmetros do método também são avaliados no cliente. O parâmetro orderID passado para o método MethodParameterExample, abaixo, é um exemplo.

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

Literais de conversão no cliente

A conversão de null em um tipo CLR é executada no cliente:

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

A conversão em um tipo, como Decimal anulável, é executada no cliente:

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

Construtores de literais

Os novos tipos CLR que podem ser mapeados para os tipos de modelo conceitual são executados no cliente:

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

As novas matrizes também são executadas no cliente.

Exceções de armazenamento

Todos os erros de armazenamento encontrados durante a execução da consulta são passados até o cliente e não são mapeados ou tratados.

Configuração do repositório

Quando a consulta é executada no repositório, a configuração do repositório substitui todos os comportamentos do cliente e a semântica do repositório é expressa para todas as operações e expressões. Isso pode resultar em uma diferença no comportamento entre a execução no CLR e no repositório em áreas como comparações nulas, pedidos de GUID, precisão de operações que envolvem tipos de dados não precisos (como tipos de ponto de flutuação ou DateTime) e operações de cadeia de caracteres. É importante lembrar-se disso ao examinar os resultados da consulta.

Por exemplo, a seguir estão algumas diferenças de comportamento entre o CLR e o SQL Server:

  • O SQL Server pede GUIDs de maneira diferente do CLR.

  • Também pode haver diferenças na precisão do resultado ao manipular o tipo decimal no SQL Server. Isso é devido aos requisitos fixos de precisão do tipo decimal do SQL Server. Por exemplo, a média dos valores Decimal 0,0, 0,0, e 1,0 é 0,3333333333333333333333333333 na memória no cliente, mas é 0,333333 no repositório (baseado na precisão padrão do tipo decimal do SQL Server).

  • Algumas operações de comparação de cadeia de caracteres também são manipuladas de maneira diferente no SQL Server em relação ao CLR. O comportamento de comparação de cadeia de caracteres depende das configurações de ordenação no servidor.

  • Chamadas de função ou de método, quando incluídas em uma consulta LINQ to Entities, são mapeadas para funções canônicas no Entity Framework, que são então convertidas em Transact-SQL e executadas no banco de dados SQL Server. Há casos em que o comportamento que essas funções mapeadas exibem pode diferir na implementação nas bibliotecas de classes base. Por exemplo, a chamada dos métodos Contains, StartsWith e EndsWith com uma cadeia de caracteres vazia como um parâmetro retornará true quando executada no CLR, mas retornará false quando executada no SQL Server. O método EndsWith também pode retornar resultados diferentes porque o SQL Server considera que duas cadeias de caracteres serão iguais se diferirem apenas no espaço em branco à direita, enquanto o CLR não as considera iguais. Isso é ilustrado pelo exemplo a seguir:

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