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 numeros = new List { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
var numerosAoQuadrado = numeros
.Where(numero => numero % 2 == 0 && numero > 5)
.Select(numero => numero * numero);
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 exemplo, as operações Where e Select são adiadas até que o loop foreach itere sobre os resultados. Essa execução adiada minimiza cálculos desnecessários e melhora a eficiência do código.
Neste código a consulta LINQ está fazendo o seguinte:
numeros.Where(num => num > 5): filtra a listanumerospara incluir apenas os números maiores que 5..Select(num => num * 2): Aplica uma operação em cada elemento da lista filtrada. Para cada elemento, está multiplicando o valor por 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
foreachseparados, tornando o código mais conciso.
IQueryable para consultar banco de dados
Ao trabalhar com bancos de dados, a LINQ pode ser otimizado ainda mais usando a interface IQueryable. Esta interface permite a criação de consultas mais eficientes, pois podem ser traduzidas em consultas SQL otimizadas.
A interface 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.
Suponha que temos uma entidade 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 consulta = context.Produtos
.Where(p => p.QuantidadeEmEstoque > 0) // Apenas produtos em estoque
.Where(p => p.Preco > 50); // Apenas produtos com preço maior que $50
// Podemos adicionar mais filtros, ordenações, projeções, etc. à consulta IQueryable
// Sem executar a consulta ainda, apenas construindo a árvore de expressão
var produtosFiltrados = consulta.ToList();
// Aqui a consulta é traduzida em uma única consulta SQL otimizada
// SELECT * FROM Produtos WHERE QuantidadeEmEstoque > 0 AND Preco > 50
foreach (var produto in produtosFiltrados)
{
Console.WriteLine($"{produto.Nome} - {produto.Preco}");
}
}
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 ToList() (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 nomes = new List { "Jair", "Alice", "Benedito", "Janice", "Carlos" };
// Utilizando LINQ para filtrar e transformar em caixa alta
List nomeFiltradosEmCaixaAlta = nomes
.Where(nome => !nome.StartsWith("J"))
.Select(nome => nome.ToUpper())
.ToList();
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:
- 2 produtos na categoria “Eletrônicos” com preços de R$1500.00 e R$1200.00
- 2 produtos na categoria “Roupas” com preços de R$89.99 e R$59.99
- 1 produto na categoria “Livros” com preço de R$29.99
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.




