Partilhar via


Execução de Consultas

Depois que uma consulta LINQ é criada por um usuário, ela é convertida em uma árvore de comandos. Uma árvore de comandos é uma representação de uma consulta que é compatível com o Entity Framework. A árvore de comandos é então executada na fonte de dados. No momento da execução da consulta, todas as expressões de consulta (ou seja, todos os componentes da consulta) são avaliadas, incluindo as expressões usadas na materialização dos resultados.

Em que ponto as expressões de consulta são executadas pode variar. As consultas LINQ são sempre executadas quando a variável de consulta é iterada, não quando a variável de consulta é criada. Isso é chamado de execução diferida. Você também pode forçar uma consulta a ser executada imediatamente, o que é útil para armazenar em cache os resultados da consulta. Isso é descrito mais adiante neste tópico.

Quando uma consulta LINQ to Entities é executada, algumas expressões na consulta podem ser executadas no servidor e algumas partes podem ser executadas localmente no cliente. A avaliação de uma expressão no lado do cliente ocorre antes que a consulta seja executada no servidor. Se uma expressão for avaliada no cliente, o resultado dessa avaliação será substituído pela 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, a manipulação de valores nulos e a precisão numérica dependem das configurações do servidor. Quaisquer exceções lançadas durante a execução da consulta no servidor são passadas diretamente para o cliente.

Gorjeta

Para obter um resumo conveniente dos operadores de consulta em 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 modo de execução (C#).

Execução de consulta adiada

Em uma consulta que retorna uma sequência de valores, a variável de consulta em si nunca mantém os resultados da consulta e armazena apenas os comandos de consulta. A execução da consulta é adiada até que a variável de consulta seja iterada em um foreach loop ou For Each . Isso é 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 repetidamente a consulta, retornando as informações atualizadas sempre.

A execução adiada permite que várias consultas sejam combinadas ou que uma consulta seja estendida. Quando uma consulta é estendida, ela é modificada para incluir as novas operações, e a eventual execução 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

Após a execução de uma consulta, todas as consultas sucessivas usarão os operadores LINQ na memória. Iterar sobre a variável de consulta usando uma foreach instrução or For Each ou chamando um dos operadores de conversão LINQ causará execução imediata. Esses operadores de conversão incluem o seguinte: ToList, ToArray, ToLookup, e ToDictionary.

Execução imediata de consultas

Em contraste com a execução adiada de consultas que produzem 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. Eles são executados imediatamente porque a consulta deve produzir uma sequência para calcular o resultado singleton. Você também pode forçar a execução imediata. Isso é útil quando você deseja armazenar em cache os resultados de uma consulta. Para forçar a execução imediata de uma consulta que não produz um valor singleton, você pode chamar o ToList método, o ToDictionary método ou o ToArray método em uma consulta ou variável de consulta. O exemplo a seguir usa o ToArray método 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 foreach loop ou For Each imediatamente após a expressão de consulta, mas chamando ToList ou ToArray armazenando em cache todos os dados em um único objeto de coleção.

Execução da Loja

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

Algumas expressões na consulta podem ser executadas no cliente. Em geral, espera-se que a maior parte da execução de consultas ocorra no servidor. Além dos métodos executados em elementos de consulta mapeados para a fonte de dados, geralmente 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.

Certas operações são sempre executadas no cliente, como vinculação de valores, subexpressões, subconsultas de fechamentos e materialização de objetos em resultados de consulta. O efeito líquido disso é que esses elementos (por exemplo, valores de parâmetro) não podem ser atualizados durante a execução. Os tipos anónimos podem ser construídos em linha na fonte de dados, mas não se deve presumir que o fazem. Agrupamentos embutidos também podem ser construídos na fonte de dados, mas isso não deve ser assumido em todas as instâncias. Em geral, é melhor não fazer suposições 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 executadas localmente, consulte Expressões em consultas LINQ to Entities.

Literais e parâmetros

As variáveis locais, como a orderID variável 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 orderID parâmetro passado para o MethodParameterExample método, 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

Lançando literais no cliente

A transmissão de para um tipo CLR é executada null 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 para um tipo, como um anulável Decimal, é 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 para literais

Novos tipos de CLR que podem ser mapeados para 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

Novas matrizes também são executadas no cliente.

Exceções da loja

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

Configuração da Loja

Quando a consulta é executada no armazenamento, 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 o CLR e a execução do armazenamento em áreas como comparações nulas, ordenação GUID, precisão e precisão de operações envolvendo tipos de dados não precisos (como tipos de ponto flutuante ou DateTime) e operações de cadeia de caracteres. É importante ter isso em mente 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 ordena GUIDs de forma diferente do CLR.

  • Também pode haver diferenças na precisão dos resultados ao lidar com o tipo decimal no SQL Server. Isso se deve aos requisitos de precisão fixa do tipo decimal do SQL Server. Por exemplo, a média dos Decimal valores 0,0, 0,0 e 1,0 é 0,3333333333333333333333333333 na memória no cliente, mas 0,333333 no armazenamento (com base na precisão padrão para o tipo decimal do SQL Server).

  • Algumas operações de comparação de cadeia de caracteres também são tratadas de forma diferente no SQL Server do que no CLR. O comportamento de comparação de cadeia de caracteres depende das configurações de agrupamento no servidor.

  • As chamadas de função ou 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 traduzidas para Transact-SQL e executadas no banco de dados do SQL Server. Há casos em que o comportamento que essas funções mapeadas exibem pode diferir da implementação nas bibliotecas de classes base. Por exemplo, chamar os Containsmétodos , StartsWithe EndsWith com uma cadeia de caracteres vazia como parâmetro retornará true quando executado no CLR, mas retornará false quando executado no SQL Server. O EndsWith método também pode retornar resultados diferentes porque o SQL Server considera duas cadeias de caracteres iguais se elas só diferem no espaço em branco à direita, enquanto o CLR as considera não iguais. Isto é ilustrado pelo seguinte exemplo:

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