![]() |
Hoje iremos recordar como a LINQ pode melhorar o desempenho das consultas na plataforma .NET. |
A LINQ - Language Integrated Query mudou a forma como os desenvolvedores interagem com dados na plataforma .NET por oferecer uma sintaxe poderosa e expressiva que permite consultar diversas fontes de dados
Além de sua legibilidade e concisão, a LINQ apresenta melhorias de desempenho que se tornam particularmente evidentes na transição do código processual tradicional para sua abordagem declarativa.
Neste artigo, vamos examniar o impacto da LINQ no código e desempenho das consultas por meio de exemplos práticos, apresentando o código antes e depois da adoção do LINQ.
Vamos aquecer as turbinas considerarando um cenário em que queremos filtrar e manipular dados sem usar a LINQ. Vamos iniciar com uma tarefa comum:
'Filtrar uma lista de inteiros e depois elevar ao quadrado cada número que atenda a uma determinada condição.'
1- Abordagem tradicional sem usar a LINQ
List<int> mumeros = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; List<int> numerosAoQuadrado = new List<int>(); foreach (int numero in mumeros) { if (numero % 2 == 0 && numero > 5) { numerosAoQuadrado.Add(numero * numero); } } |
Este código cria uma lista de números de 1 a 10, e então cria uma segunda lista chamada numerosAoQuadrado que contém os quadrados de todos os números pares maiores que 5 da lista original. Em seguida, imprime os números dessa segunda lista.
Embora o código acima atinja o resultado desejado, falta-lhe a elegância e expressividade que a LINQ traz para a mesa. Agora, vamos refazer a mesma tarefa usando LINQ.
List |
Essas alterações simplificam o código e o tornam mais eficiente, utilizando as capacidades do LINQ para filtrar e mapear os números conforme necessário. Isso foi apenas uma amostra do que a LINQ pode fazer pelo seu código.
Vejamos agora como a LINQ usa a execução adiada ou Deferred Execution.
Deferred Execution
A LINQ introduz o conceito de execução adiada, o que significa que
a execução de uma consulta é adiada até que os resultados reais sejam
necessários. Isto pode levar a melhorias significativas de desempenho,
especialmente ao lidar com grandes conjuntos de dados.
Exemplo:
List<int> numeros = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
var consulta = numeros.Where(num => num > 5).Select(num => num * 2); foreach (var resultado in consulta) { Console.WriteLine(resultado); } |
Neste código a consulta LINQ está fazendo o seguinte:
numeros.Where(num => num > 5)
numeros para incluir apenas os números maiores que 5.
.Select(num => num * 2)
Assim, a variável consulta
é uma coleção resultante dessas operações, e, ela contém os números maiores que 5
multiplicados por 2.
A seguir ocódigo itera sobre a consulta para imprimir cada resultado no console.
Este código tem algumas vantagens em relação ao código original:
Clareza: A sintaxe do LINQ tende a ser mais clara e expressiva, tornando mais fácil entender o que está sendo feito com os dados.
Menos código: Com LINQ, você pode realizar operações de
filtro e projeção em uma única linha de código, ao invés de usar loops
foreach separados, tornando o código mais conciso.
IQueryable é
fundamental para a otimização de consultas LINQ quando se trabalha com bancos de
dados. Ela permite que as consultas LINQ sejam construídas de forma que o Entity
Framework, por exemplo, possa traduzi-las diretamente em consultas SQL mais
eficientes e otimizadas, executando partes da consulta diretamente no banco de
dados em vez de trazer todos os dados para a aplicação e filtrar localmente.Produto e queremos obter todos os
produtos que estão em estoque e têm um preço superior a R$50,00. Podemos usar a
LINQ e a interface IQueryable para criar uma consulta que seja
traduzida em SQL otimizado.
using (var context = new ApplicationContext()) { // Consulta inicial, usando IQueryable IQueryable |
Neste exemplo, usamos a interface
IQueryable para construir a consulta, adicionando vários
critérios de filtro (Where)
à medida que construímos a consulta. A consulta só é executada quando chamamos
()
(ou métodos similares como
FirstOrDefault(),
Single(), etc.). No momento em que
ToList()
é chamado, a consulta é traduzida em uma única consulta SQL otimizada e é
executada no banco de dados, retornando apenas os resultados desejados.
Isso ilustra como a interface
IQueryable permite construir consultas de forma mais
flexível e como o Entity Framework pode
traduzir essas consultas em SQL eficiente para executar no banco de dados,
minimizando a quantidade de dados trazidos para a aplicação e melhorando a
performance.
Filtrando e Transformando Strings
Considere um cenário em que você tem uma lista de
nomes e deseja filtrar os nomes que começam com a letra ‘J’ e transformar os
nomes restantes em letras maiúsculas.
Abordagem tradicional sem LINQ:
List<string> nomes = new List<string> { "Jair", "Alice", "Benedito", "Janice", "Carlos" };
List<string> nomeFiltradosEmCaixaAlta = new List<string>(); foreach (string nome in nomes) { if (!nome.StartsWith("J")) { nomeFiltradosEmCaixaAlta.Add(nome.ToUpper()); } } |
Vejamos agora o mesmo código usando LINQ:
List |
Neste código otimizado, estamos usando os métodos de extensão
Where
e Select da LINQ:
.Where(nome =>
!nome.StartsWith("J")): filtra a lista de nomes
para incluir apenas aqueles que não começam com a letra "J".
.Select(nome => nome.ToUpper()):
Transforma os nomes filtrados em caixa alta.
.ToList():
Converte o resultado da consulta LINQ em uma lista de strings.
Agrupamento
Agora digamos que você tenha uma lista de produtos, cada um com uma categoria e preço, e queira calcular o preço médio de cada categoria.
O código tradicional geralmente usado para tratar com este problema é visto abaixo:
List<Produto> produtos = GetProdutos(); Dictionary<string, double> precosMedios = new Dictionary<string, double>(); Dictionary<string, int> contaProdutos = new Dictionary<string, int>(); foreach (Produto produto in produtos) { if (!precosMedios.ContainsKey(produto.Categoria)) { precosMedios.Add(produto.Categoria, 0); contaProdutos.Add(produto.Categoria, 0); } precosMedios[produto.Categoria] += produto.Preco; contaProdutos[produto.Categoria]++;} foreach (string categoria in precosMedios.Keys.ToList()) { precosMedios[categoria] /= contaProdutos[categoria]; } Console.ReadKey(); static List<Produto> GetProdutos() { List<Produto> produtos = new List<Produto> { new Produto { Categoria = "Eletrônicos", Preco = 1500.00 }, new Produto { Categoria = "Eletrônicos", Preco = 1200.00 }, new Produto { Categoria = "Roupas", Preco = 89.99 }, new Produto { Categoria = "Roupas", Preco = 59.99 }, new Produto { Categoria = "Livros", Preco = 29.99 } }; return produtos; } public class Produto { public string? Categoria { get; set; } public double Preco { get; set; } } |
Agora temos a seguir o código otimizado usando a LINQ :
List<Produto> produtos = new List<Produto> { new Produto { Categoria = "Eletrônicos", Preco = 1500.00 }, new Produto { Categoria = "Eletrônicos", Preco = 1200.00 }, new Produto { Categoria = "Roupas", Preco = 89.99 }, new Produto { Categoria = "Roupas", Preco = 59.99 }, new Produto { Categoria = "Livros", Preco = 29.99 } }; // Agrupando os produtos por categoria var produtosAgrupadosPorCategoria = produtos.GroupBy(produto => produto.Categoria); // Calculando o preço médio de cada categoria var precosMedios = produtosAgrupadosPorCategoria .ToDictionary( grupo => grupo.Key, grupo => grupo.Average(produto => produto.Preco) ); foreach (var categoria in precosMedios) { Console.WriteLine($"Categoria: {categoria.Key}, Preço Médio: {categoria.Value:C2}"); } Console.ReadKey(); public class Produto { public string? Categoria { get; set; } public double Preco { get; set; } } |
Explicando o código otimizado:
produtos.GroupBy(produto =>
produto.Categoria): Agrupa os produtos por
categoria, criando um conjunto de grupos onde cada grupo contém todos os
produtos de uma categoria específica.
ToDictionary(...):
Converte o resultado do agrupamento em um dicionário onde a chave é a
categoria e o valor é o preço médio daquela categoria.
grupo.Key:
Representa a chave do dicionário, que neste caso é a categoria.grupo.Average(produto =>
produto.Preco): Calcula a
média dos preços dentro de cada grupo de produtos, ou seja, o preço
médio da categoria.Dessa forma, evitamos a necessidade de criar e atualizar manualmente dicionários separados para armazenar os totais e as contagens, e conseguimos calcular os preços médios diretamente usando a funcionalidade de agrupamento e média da LINQ. Isso torna o código mais legível, conciso e eficiente.
Neste exemplo, temos:
O código calculará os preços médios para cada categoria e os imprimirá no console. Ao executar o programa, você deve ver a seguinte saída:

Na segunda parte do artigo vamos continuar analisando outros exemplos de otimização com LINQ.
E estamos conversados...![]()
"Estai,
pois, firmes na liberdade com que Cristo nos libertou, e não torneis a
colocar-vos debaixo do jugo da servidão."
Gálatas 5:1
Referências: