LINQ - Entendendo Deferred Execution ou execução adiada
No artigo de hoje eu vou abordar os conceitos sobre Deferred Execution em consultas LINQ, explicando como esse recurso funciona, e porque você deve entender bem esse conceito para poder usar o recurso de forma adequada em suas aplicações. |
Vamos começar com a tradução de Deferred Execution que em português seria
algo como execução adiada, postergada, retardada, etc.
O importante é que fique claro o entendimento de que o termo significa uma execução que não foi realizada no momento e foi adiada para ser executada posteriormente.
Quando usamos consultas LINQ (Language Integrated Query), elas podem possuir dois comportamentos de execução distintos os quais são:
LINQ -
Language integrated Query - é um conjunto de recursos introduzidos
no .NET Framework 3.5 que permitem a realização de consultas diretamente em base de dados, documentos XML , estrutura de dados , coleção de objetos ,etc. usando uma sintaxe parecida com a linguagem SQL. |
Execução Deferred ou Adiada
Execução Imediata
Quando o LINQ executa uma consulta ele utiliza a execução adiada ou Deferred Execution, e, isso significa que a consulta real não é executada até que os dados sejam realmente requisitados e acessados pela iteração e não quando a consulta é criada. Esse é o comportamento padrão do LINQ.
Obs: Em outras palavras : "Uma consulta LINQ que contém somente
métodos com execução adiada, não será executada até que os itens no resultado
sejam enumerados"
Para este exemplo
vamos criar uma aplicação do tipo Console Application usando o o
VS 2013
Express for windows desktop. Vamos criar uma aplicação usando a linguagem C# e outra usando a linguagem VB .NET. |
Como assim ?
Vamos explicar usando um exemplo de uma consulta LINQ bem simples. Considere o código abaixo:
Execução Adiada ou Deferred:
int[] numeros = { 1, 2, 3, 4, 5 ,6 ,7};
int i = 3;
var resultado = from n in numeros
foreach (var n in resultado) Console.ReadKey();
|
Dim numeros As Integer() = {1, 2, 3, 4, 5, 6, 7}
Dim i As Integer = 3
Dim resultado = From n In numeros Where n <= i Select n For Each n In resultado Console.WriteLine(n) Next Console.ReadKey()
|
A consulta LINQ usada neste exemplo é a seguinte:
var resultado = from n in numeros
|
Dim resultado = From n In numeros
Where n <= i
Select n
|
Você poderia supor que este código executa a consulta LINQ. Mas não é isso o que
ocorre. Este código apenas capta a ideia da consulta em uma estrutura de dados
chamada uma árvore de expressão ou expression tree.
A árvore contém informações sobre a classe/tabela/lista que você deseja
consultar, a consulta que você quer fazer e o resultado que você deseja
retornar. Porém a consulta não é executada.
Vamos analisar a estrutura da consulta em mais detalhes:
O operador Where é usado para filtrar os resultados de uma pesquisa baseada em uma condição especificada. (Where n <= i)
No código acima, o operador Where instrui a consulta para recuperar somente os valores que são inferiores ou iguais a qualquer que seja o valor de i.
No código exemplo atribuímos o valor 3 à variável i. ( i=3 )
Portanto, quando a expressão de consulta for executada, o operador Where vai comparar cada número da lista com 3 verificando se ele é menor ou igual a ele.
Em seguida, os números que atenderem a condição do operador Where serão incluídos no resultado.
Você deve estar assumindo agora que a variável resultado contém valores de 1, 2 e 3, mas, na verdade, ele contém apenas um cálculo de como você pode obter esses valores.
Quando realizamos a iteração na variável resultado usando um laço foreach/For Each é que a consulta é executada e o resultado é exibido:
No nosso exemplo a consulta é executada quando o laço foreach/For Each é executado:
foreach (var n in
resultado) <== (Executa Aqui) |
For Each n In resultado <== (Executa Aqui)
Console.WriteLine(n)
Next
|
O resultado obtido é mostrado abaixo:
Bem , você pode não estar convencido de que tudo isso que foi dito é verdade, pois o resultado é o esperado.
Então como podemos provar que a consulta é executada somente quando o laço foreach/For Each é executado ?
Elementar meu caro, vamos alterar o código atribuindo um novo valor à variável i após a consulta ser definida e antes do laço foreach/For Each ser executado. Veja abaixo como ficou o código:
Execução Adiada:
int[] numeros = { 1, 2, 3, 4, 5 ,6 ,7};
int i = 3;
var resultado = from n in numeros i=4;
foreach (var n in resultado) Console.ReadKey();
|
Dim numeros As Integer() = {1, 2, 3, 4, 5, 6, 7}
Dim i As Integer = 3
Dim resultado = From n In numeros Where n <= i Select n i=4 For Each n In resultado Console.WriteLine(n) Next Console.ReadKey()
|
Executando o código novamente iremos obter o seguinte resultado:
Veja que o resultado mudou, logo o fato de eu alterar o valor da variável i, mesmo fazendo isso depois da consulta, alterou o resultado da consulta. Isso prova que a execução foi adiada.
Alteramos o valor da variável i para 4.
E isso afeta diretamente o resultado da expressão da consulta por causa da execução adiada ou Deferred Execution.
Quando modificamos o valor de i, a expressão da consulta foi atualizada e o resultado apresenta os valores de 1 a 4 e não de 1 a 3;
Por causa da Deferred Execution o programa tem permissão para atualizar a expressão da consulta antes de executar para obter os valores.
A Execução adiada é o comportamento padrão do LINQ, mas você também pode fazer com que a consulta tenha a execução imediata.
Para fazer isso você pode usar um outro conjunto de métodos do namespace System.Linq que é composto por ToArray<T>(), ToList<T>(), ToDictionary<T>(), e ToLookup<T>(), Count, etc.
Nota : Na relação abaixo temos os métodos de extensão usados na LINQ que possuem execução adiada (deferred)
|
Existem também os métodos de extensão para a interface IEnumerable<T>.
Por exemplo, você pode usar o método ToList<T>() para converter o
resultado da expressão da consulta em uma coleção List<T>.
Veja no código abaixo como podemos usar ToList() para executar
imediatamente a expressão de consulta:
Execução Imediata:
int[] numeros = { 1, 2, 3, 4, 5 ,6 ,7};
int i = 3;
var
resultado = (from n in numeros i=4;
foreach (var n in resultado) Console.ReadKey();
|
Dim numeros As Integer() = {1, 2, 3, 4, 5, 6, 7}
Dim i As Integer = 3
Dim resultado = (From n In numeros Where n <= i Select n).ToList() i=4 For Each n In resultado Console.WriteLine(n) Next Console.ReadKey()
|
Agora executando o código acima teremos:
Agora com a alteração feita, a expressão da consulta foi executada imediatamente
quando o método ToList() foi chamado, e, dessa forma, a variável
resultado já contém os valores finais da consulta.
Isso pode ser confirmado pelo fato de que mesmo tendo alterado o valor da
variável i após a consulta o resultado não foi alterado.
A execução adiada é um comportamento padrão do LINQ em todas as suas implementações : LINQ to SQL, LINQ to Objects, LINQ to Entities, LINQ to Xml, etc.
Assim, se você não compreender corretamente esse comportamento do LINQ, o resultado das suas consultas pode ser afetado e você nem perceber o motivo pelo qual isso ocorreu.
Mas faço-vos saber, irmãos, que o
evangelho que por mim foi anunciado não é segundo os homens.
Porque não o recebi, nem aprendi de homem algum, mas pela revelação de Jesus
Cristo.
Gálatas 1:11-12
Referências: