LINQ to Entities - 6 dicas legais para escrever consultas


Hoje vou apresentar seis dicas que podemos usar para criar consultas melhores usando o LINQ to Entities.

A LINQ é uma ferramenta de consulta poderosa que podemos usar em aplicativos .NET. Existem certas técnicas a serem seguidas ao escrever consultas para garantir que sejam executadas de forma rápida e eficaz.

A LINQ to Entities fornece suporte a LINQ que permite aos desenvolvedores escreverem consultas no modelo conceitual do Entity Framework usando o C#. As consultas no Entity Framework são representadas por consultas de árvore de comando, que são executadas no contexto de objeto.

Assim, a LINQ to Entities converte consultas da LINQ  para consultas de árvore de comando, executa as consultas no Entity Framework e retorna os objetos que podem ser usados pelo Entity Framework e pela LINQ

A seguir vou apresentar alguns itens a serem considerados quando se pretende melhorar o desempenho das consultas criadas usando  LINQ to Entities.

1- Use somente as colunas necessárias

Parece óbvio mas ao criar uma consulta procure extrair apenas as colunas necessárias na cláusula Select ao invés de carregar todas as colunas da tabela em questão.

Vejamos um trecho de código que você pode encontrar com muita frequência em exemplos:

using (var context = new AppDbContext())
{

 
  var produtos = context.Products;
 
    ...
}

Esta pequena linha de código:  var produtos = context.Produtos;

diz ao Entity Framework para ir para a tabela Produtos e obter TODAS as linhas e colocá-las em objetos C#.

Assim se esta tabela tiver 100 colunas com 1000000 de registros essa linha de código vai tentar retornar toda essa informação de uma vez. (No caso os dados serão obtidos quando forem iterados)

Uma opção melhor seria restringir o número de colunas retornadas:

using (var context = new AppDbContext())
{
     var produtos = context.Products
                             .Where(p => p.UnitPrice > 100)
                             .Select(p => new
                             {
                                    p.ProductName,
                                    p.UnitsInStock,
                                    p.QuantityPerUnit,
                                    p.UnitPrice
                              }).ToList();
  }

Aqui estamos restringindo a quantidade de registros definindo um critério para o preço e também a quantidade de colunas definindo um objeto do tipo produto na cláusula Select que retorna apenas algumas colunas.

2- Utilize IQueryable e os operadores Skip/Take em consultas com interação

Ao trabalhar com uma grande coleção de dados e vinculá-la a uma tabela ou controle , não devemos carregar todos os registros para o usuário em uma única instância porque isso leva muito tempo.

Em vez disso, podemos carregar um certo número de registros inicialmente, digamos 10 ou 20. Quando o usuário deseja ver o próximo conjunto de registros, podemos carregar os próximos 10 ou 20 registros sob demanda.

O tipo IQueryable em C# pode conter consultas SQL não avaliadas, que podem ser posteriormente convertidas para serem executadas na coleção de dados após a aplicação de skip and take.

Exemplo de consulta LINQ usando IQueryable e skip e take:

 private static IEnumerable<object> Teste(int skip, int take, int categoria, int estoqueMinimo, AppDbContext context)
 {
          //selecione todos os produtos e cria uma coleção IQueryable
            IQueryable<Products> products = context.Products;
            // Cria uma consulta dinamica na coleção Queryable
            if (categoria > 0)
                products = products.Where(a => a.CategoryId == categoria);
            // Cria uma consulta dinamica na coleção Queryable
            if (estoqueMinimo > 0)
                products = products.Where(a => a.UnitsInStock > estoqueMinimo);
            //Aplica skip e take e carrega os registros
            return products.OrderBy(a => a.ProductId)
                           .Skip(skip)
                           .Take(take)
                           .Select(p => new
                           {
                               p.ProductName,
                               p.UnitsInStock,
                               p.QuantityPerUnit,
                               p.UnitPrice
                           }).ToList();
  }

3- Use left join e inner join no local correto

O local onde aplicar o left join e o inner join também desempenham um papel vital na execução da consulta.

Quando não temos certeza se os registros na Tabela A terão registros correspondentes na Tabela B, devemos usar a left join.

Quando tivermos certeza de que haverá registros relacionais em ambas as tabelas, devemos usar inner join.

Escolher o tipo certo de junção para estabelecer uma relação entre tabelas é importante, pois várias tabelas com consultas inner join são executadas melhor do que várias tabelas com left join.

Portanto, devemos identificar nossos requisitos e usar a junção (esquerda ou interna) para uma melhor execução da consulta.

4- Use o recurso AsNoTracking quando possível

Quando carregamos registros de um banco de dados por meio de consultas LINQ-to-Entities, e realizarmos o processamento e a seguir fazemos atualizações de volta no banco de dados as entidades tem que ser rastreadas para controlar essas operações.

Quando estamos realizando apenas operações de leitura, e não faremos nenhuma atualização no banco de dados não precisamos que as entidades sejam rastreadas e isso reduz o consumo de memória.

Nestes casos podemos criar consultas LINQ usando o recurso AsNoTracking que desabilita o rastreamento das entidades:

   using (var context = new AppDbContext())
   {
           var produtos = context.Products
                      .Where(p => p.UnitPrice > 100)
                      .AsNoTracking();
    }

5- Use operações assíncronas nas entidades

As entidades oferecem as seguintes operações assíncronas:

ToListAsync(): recupera a coleção de dados de forma assíncrona.
CountAsync(): recupera a contagem de dados de forma assíncrona.
FirstAsync(): recupera o primeiro conjunto de dados de forma assíncrona.
SaveChangesAsync(): Salva as alterações da entidade de forma assíncrona.

As operações assíncronas são usadas em locais específicos nos aplicativos para reduzir o bloqueio da thread da interface do usuário. Eles aprimoram a IU tornando-a responsiva.

 using (var context = new AppDbContext())
  {
                var produtos = await context.Products
                                        .Where(p => p.UnitPrice > 100)
                                         .ToListAsync();
 }

6- Verifique a consulta SQL enviada ao banco de dados

Verificar uma consulta SQL antes de enviá-la a um banco de dados é a coisa mais importante que você pode fazer ao tentar melhorar o desempenho de uma consulta LINQ-to-Entities.

Todos nós sabemos que a consulta LINQ-to-Entities será convertida em uma consulta SQL e executada em um banco de dados.

Uma consulta SQL gerada como resultado de uma consulta LINQ será eficaz para um melhor desempenho.

Para poder exibir no console as consultas SQL geradas no EF Core 5 podemos definir no arquivo de contexto a utilização dos métodos:

Abaixo temos um exemplo de código mostrando a utilização dos métodos para exibir as consultas SQL no console:

public class AppDbContext : DbContext
{
    private readonly string _connectionString;
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
         optionsBuilder.UseSqlServer(_connectionString)
            .LogTo(WriteLine, new[] { RelationalEventId.CommandExecuted })
               .EnableSensitiveDataLogging();
    }
   ...
}

Exemplo de código gerado para a consulta:

var produtos = await context.Products.Where(p => p.UnitPrice > 10).ToListAsync();



Podendo analisar a consulta gerada você verifica se ela precisa ser otimizada.

Pegue o projeto completo aqui : LINQ_Queries.zip (sem as referências)

"Quem, pois, tiver bens do mundo, e, vendo o seu irmão necessitado, lhe cerrar as suas entranhas, como estará nele o amor de Deus? Meus filhinhos, não amemos de palavra, nem de língua, mas por obra e em verdade."
1 João 3:17,18

Referências:


José Carlos Macoratti