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