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:
.NET - Procurando arquivos com LINQ
C# - Um pouco mais sobre LINQ Queries
LINQ - Revisando conceitos básicos
LINQ - Consultas LINQ