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.
  1. Execução Deferred ou Adiada

  2. 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
                              where n <= i
                              select n;

 

        foreach (var n in resultado)
        {
              Console.WriteLine(n);
        }

        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  
                              where n <= i
                              select n;

        
        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:

No nosso exemplo a consulta é executada quando o laço foreach/For Each é executado:

 

    foreach (var n in resultado) <== (Executa Aqui)
    {
              Console.WriteLine(n);
   }

        
          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
                              where n <= i
                              select n;

         i=4;

 

        foreach (var n in resultado)
        {
              Console.WriteLine(n);
        }

        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.

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)

  • OrderBy
  • OrderByDescending
  • Reverse
  • Select
  • SelectMany
  • Skip
  • SkipWhile
  • Take
  • TakeWhile
  • Where

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
                                 where n <= i
                                 select n).ToList();

         i=4;

 

        foreach (var n in resultado)
        {
              Console.WriteLine(n);
        }

        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:


José Carlos Macoratti