EF Core - 3 Dicas para um bom desempenho


 Neste artigo veremos 3 dicas para obter um bom desempenho com o Entity Framework Core.


O desempenho do banco de dados é um tópico vasto e complexo, abrangendo uma pilha inteira de componentes: o banco de dados, a rede, o driver de banco de dado e as camadas de acesso aos dados como o EF Core.


 

O EF Core é usado para fornecer uma camada de abstração entre o mundo relacional e o mundo dos objetos permitindo que o desenvolvedor abstraia muitos conceitos do mundo dos banco de dados relacionais focando na programação orientada a objetos.


Assim, podemos usar o EF Core com esse objetivo mas não podemos relegar a segundo plano outros quesitos importantes como desempenho.

 

Para mostrar os exemplos das dicas, eu vou usar um projeto Console onde já temos definidos o EF Core 5.0 e o mapeamento realizado usando a Fluent API para as entidades : Pedido, Item, Cliente e Produto.


Nota:  Para detalhes da criação do projeto e do mapeamento usando a Fluent API veja o seguinte artigo : EF Core - Sistema de Vendas : Usando a Fluent API

 

Assim o nosso modelo de entidades possui as seguintes definições e associações :
 


A seguir vou apresentar 3 dicas básicas que com certeza vão melhorar o desempenho das consultas geradas pelo EF Core.

 

1-  Obtenha somente os dados que você precisa

 

Um dos erros básicos nas consultas LINQ usadas nas aplicações com EF Core é precisar de uma quantidade x de dados e tratar com uma quantidade muito maior. Assim se sua consulta produz muito mais dados do que você precisa isso é um desperdício.

 

a- Exemplo de consulta RUIM

 

private static void Main(string[] args)
{
  using var contextoDb = new ApplicationDbContext();

  var clientes = contextoDb
                .Clientes
                .Where(c => c.Estado == "SP")
                .ToList();

  var primeiroCliente = clientes[0];
  var segundoCliente = clientes[1];

           

  Console.WriteLine($" - {primeiroCliente.Nome} \n - {segundoCliente.Nome}");
  Console.ReadLine();
}

 

Na consulta usada neste exemplo estamos retornando todos clientes e seus dados apenas para obter dois clientes.

 

b- Exemplo de consulta MELHOR

 

private static void Main(string[] args)
{
  using var contextoDb = new ApplicationDbContext();

  var clientes = contextoDb
                .Clientes
                .Where(c => c.Estado == "SP")
            
   .Take(2)
                .ToList();

  var primeiroCliente = clientes[0];
  var segundoCliente = clientes[1];
           

  Console.WriteLine($" - {primeiroCliente.Nome} \n - {segundoCliente.Nome}");
  Console.ReadLine();
}

 

Usando o operador Take(2) estamos obtendo apenas dois clientes como desejamos.
 

Outros operadores que podemos usar para restringir a quantidade de dados nas consultas :

2-  Considere o uso de IQueryable e IEnumerable

 

O primeiro ponto importante a destacar é que a interface IQueryable herda de IEnumerable , de forma que tudo que IEnumerable pode fazer, IQueryable também pode.
 

- IEnumerable<T> é ótimo para trabalhar com sequências que são iteradas na memória;
 

- IQueryable<T> permite trabalhar sem usar a memória, como uma fonte de dados remota, como um banco de dados ou serviço da web;
 

- IQueryable é uma definição de consulta e não será carregado se não for necessário. Estender este objeto apenas altera a forma como o EF gera o SQL.


- IEnumerable é uma coleção de dados que podem ser enumerados e ela será carregada assim que você acessá-lo.

A Grande diferença entre IEnumerable e  IQueryable é onde o filtro é executado. O primeiro executa no cliente e outro executa no banco de dados.

Assim você tem que decidir quando usar IQueryable ou IEnumerable dependendo do cenário.

Resumindo:

- IEnumerable é indicando quando todas as consultas são realizadas na memória.
- IEnumerable é usado principalmente para LINQ to Object e LINQ to XML.
-  Ao consultar dados do banco de dados, IEnumerable executa "consulta de seleção" no lado do servidor, carrega os dados na memória no lado do cliente e, em seguida, filtra os dados;

- IQueryable permite estender as consultas;
- Ao consultar dados de um banco de dados, IQueryable executa uma "consulta de seleção" no lado do servidor com todos os filtros;

3-  Carregar entidades relacionadas com adiantamento quando possível

Ao lidar com entidades relacionadas, geralmente sabemos com antecedência o que precisamos carregar: um exemplo típico seria carregar um determinado conjunto de Clientes, juntamente com todos seus Pedidos.

 

Nesses cenários, é sempre melhor usar o carregamento adiantado ou eager loading para que o EF possa buscar todos os dados necessários em um único ida e volta.

 

O recurso  filtered include, introduzido no EF Core 5,0, também permite que você limite quais entidades relacionadas você gostaria de carregar, mantendo o processo de carregamento adiantado factível em um único ida e volta.

 

A seguir temos um exemplo usando o novo recurso Filtered Include.

 

Ao usar o include  para carregar dados relacionados, você pode adicionar certas operações enumeráveis à navegação de coleção incluída, o que permite a filtragem e classificação dos resultados.

As operações com suporte são: Where, OrderBy, OrderByDescending, ThenBy, ThenByDescending, Skip e Take.

Tais operações devem ser aplicadas na navegação da coleção no lambda passado para o método Include, conforme mostrado no exemplo abaixo:

 

private static void Main(string[] args)
{
  using var contextoDb = new ApplicationDbContext();

  var clientesPedidos =  contextoDb.Clientes
                     
  .Include(p => p.Pedidos
                    
   .Where(c => c.ClienteId >1)
                        .
OrderByDescending(p => p.DataPedido)
                        .
Take(1))
                        .ToList();

 Console.ReadLine();
}

E estamos conversados...

"Confessai as vossas culpas uns aos outros, e orai uns pelos outros, para que sareis. A oração feita por um justo pode muito em seus efeitos."
Tiago 5:16

Referências:


José Carlos Macoratti