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