Consultas LINQ e Conceitos Avançados em C#

1. Introdução ao LINQ

Preparando os Dados

Para demonstrar os recursos do LINQ, vamos definir algumas classes de modelo simples. A classe Livro representa uma obra literária.

public class Livro
{
    public string Titulo { get; set; }
    public string Autor { get; set; }
    public string Genero { get; set; }
    public int AnoPublicacao { get; set; }
}

A classe estática DadosBiblioteca fornece dados de exemplo.

public static class DadosBiblioteca
{
    public static IEnumerable<Livro> ObterCatalogo()
    {
        return new List<Livro>
        {
            new Livro { Titulo = "Dom Casmurro", Autor = "Machado de Assis", Genero = "Romance", AnoPublicacao = 1899 },
            new Livro { Titulo = "O Cortiço", Autor = "Aluísio Azevedo", Genero = "Romance Naturalista", AnoPublicacao = 1890 },
            new Livro { Titulo = "Grande Sertão: Veredas", Autor = "Guimarães Rosa", Genero = "Romance", AnoPublicacao = 1956 },
            new Livro { Titulo = "Vidas Secas", Autor = "Graciliano Ramos", Genero = "Romance Regionalista", AnoPublicacao = 1938 },
            new Livro { Titulo = "Iracema", Autor = "José de Alencar", Genero = "Romance Indianista", AnoPublicacao = 1865 },
            new Livro { Titulo = "O Alienista", Autor = "Machado de Assis", Genero = "Conto", AnoPublicacao = 1882 },
            new Livro { Titulo = "Capitães da Areia", Autor = "Jorge Amado", Genero = "Romance", AnoPublicacao = 1937 },
            new Livro { Titulo = "A Moreninha", Autor = "Joaquim Manuel de Macedo", Genero = "Romance", AnoPublicacao = 1844 }
        };
    }
}

Consultas LINQ Básicas

Uma consulta LINQ para selecionar livros do gênero "Romance" e ordená-los pelo título pode ser expressa com sintaxe de consulta ou com métodos de extensão (lambda).

// Sintaxe de consulta
var consulta =
    from livro in DadosBiblioteca.ObterCatalogo()
    where livro.Genero == "Romance"
    orderby livro.Titulo
    select livro;

// Sintaxe de método (equivalente)
var consultaAlternativa = DadosBiblioteca.ObterCatalogo()
    .Where(l => l.Genero == "Romance")
    .OrderBy(l => l.Titulo);

Execução Adiada

A variável consulta não contém os resultados. Ela define um plano de execução. A consulta é realmente executada apenas quando a sequência é iterada, por exemplo, em um loop foreach ou ao chamar um operador de agregação como Count().

2. Operadores de Consulta Padrão

Filtragem com Índice

A sobrecarga do operador Where pode receber um índice, útil para filtrar com base na posição do elemento na sequência.

var livrosImparesPorIndice = DadosBiblioteca.ObterCatalogo()
    .Where((livro, indice) => livro.Autor.StartsWith("G") && indice % 2 != 0);

Filtragem por Tipo

O operador OfType<T> filtra os elementos de uma sequência com base em um tipo específico.

object[] dadosMistos = { "Texto", 10, 20, "Outro Texto", 30 };
var numeros = dadosMistos.OfType<int>(); // Resultado: 10, 20, 30

Seleção Aninhada (SelectMany)

Quando um objeto contém uma coleção, SelectMany projeta cada elemento da fonte e achata as sequências resultantes em uma única sequência.

// Supondo que a classe Livro tenha uma propriedade IEnumerable<string> Capitulos
var todosOsCapitulos = DadosBiblioteca.ObterCatalogo()
    .SelectMany(livro => livro.Capitulos);

Agrupamento

O operador GroupBy organiza os elementos em grupos com base em uma chave. O resultado é uma sequência de objetos IGrouping<TKey, TElement>.

var livrosPorAutor = from livro in DadosBiblioteca.ObterCatalogo()
                     group livro by livro.Autor into grupoPorAutor
                     orderby grupoPorAutor.Key
                     select new
                     {
                         Autor = grupoPorAutor.Key,
                         Quantidade = grupoPorAutor.Count(),
                         Titulos = grupoPorAutor.Select(l => l.Titulo)
                     };

Junção Interna

A junção combina elementos de duas sequências com base em uma chave de correspondência.

// Exemplo com dados de vendas fictícios
var vendas = new[] { new { LivroId = 1, Data = DateTime.Now }, ... };
var detalhesLivros = DadosBiblioteca.ObterCatalogo()
    .Select((l, idx) => new { Livro = l, Id = idx + 1 });

var resultadoJuncao = from venda in vendas
                      join detalhe in detalhesLivros on venda.LivroId equals detalhe.Id
                      select new { detalhe.Livro.Titulo, venda.Data };

Junção Externa Esquerda

Uma junção externa esquerda retorna todos os elementos da sequência da esquerda e os elementos correspondentes da direita. Elementos sem correspondência na direita têm valor null.

var resultadoEsquerda = from detalhe in detalhesLivros
                        join venda in vendas on detalhe.Id equals venda.LivroId into vendasGrupo
                        from venda in vendasGrupo.DefaultIfEmpty()
                        select new
                        {
                            detalhe.Livro.Titulo,
                            DataVenda = venda?.Data
                        };

Operações de Conjunto

Os operadores Distinct, Union, Intersect e Except realizam operações de teoria dos conjuntos nas sequências.

// Obter gêneros únicos
var generosUnicos = DadosBiblioteca.ObterCatalogo()
    .Select(l => l.Genero)
    .Distinct();

Particionamento

Os operadores Take e Skip são úteis para paginação.

int tamanhoPagina = 3;
int paginaAtual = 0; // Primeira página
var livrosPagina1 = DadosBiblioteca.ObterCatalogo()
    .Skip(paginaAtual * tamanhoPagina)
    .Take(tamanhoPagina);

Operadores de Agregação

Operadores como Count, Sum, Min, Max e Average produzem um único valor escalar a partir de uma sequência.

int totalDeLivros = DadosBiblioteca.ObterCatalogo().Count();
int anoMaisAntigo = DadosBiblioteca.ObterCatalogo().Min(l => l.AnoPublicacao);

Operadores de Conversão

Os operadores ToList, ToArray e ToDictionary forçam a execução imediata da consulta e armazenam os resultados em uma coleção específica.

List<Livro> listaDeLivros = DadosBiblioteca.ObterCatalogo()
    .Where(l => l.AnoPublicacao > 1900)
    .ToList();

3. LINQ Paralelo (PLINQ)

O PLINQ é uma implementação paralela do LINQ to Objects que pode acelerar consultas em grandes conjuntos de dados, distribiundo o trabalho por vários processadores.

var dadosGrandes = Enumerable.Range(0, 1000000);

// Consulta sequencial
var resultadoSeq = from n in dadosGrandes where ComputacaoIntensiva(n) select n * 2;

// Consulta paralela
var resultadoPar = from n in dadosGrandes.AsParallel() where ComputacaoIntensiva(n) select n * 2;

Métodos como WithDegreeOfParallelism e WithExecutionMode permitem controlar o comportamento paralelo. Para cancelar uma operação de longa duração, passe um CancellationToken com WithCancellation.

4. Árvores de Expressão

Árvores de expressão representam código como uma estrutura de dados em árvore. Elas são cruciais para provedores LINQ como o LINQ to SQL, que traduzem a consulta para uma linguagem como SQL.

Uma expressão lambda como (a, b) => a + b pode ser compilada em um delegado (executado na memória) ou representada como uma árvore de expressão (analisada por um provedor).

// Criando uma árvore de expressão para (a, b) => a + b
ParameterExpression paramA = Expression.Parameter(typeof(int), "a");
ParameterExpression paramB = Expression.Parameter(typeof(int), "b");
BinaryExpression soma = Expression.Add(paramA, paramB);
Expression<Func<int, int, int>> expressaoLambda = Expression.Lambda<Func<int, int, int>>(soma, paramA, paramB);

// Podemos compilar a árvore para obter um delegado executável
Func<int, int, int> funcaoSoma = expressaoLambda.Compile();
int resultado = funcaoSoma(3, 4); // 7

Muitas bibliotecas, como o Entity Framework, usam árvores de expressão para analisar consultas LINQ e convertê-las em comandos nativos do banco de dados.

5. Provedores LINQ

Um provedor LINQ implementa o padrão IQueryable<T> para um data source específico (banco de dados, XML, etc.). A interface IQueryable herda de IEnumerable.

Ao escrever uma consulta contra um IQueryable, o compilador escolhe métodos de extensão da classe Queryable (que aceitam Expression<Func<...>>) em vez da classe Enumerable (que aceita Func<...>). Isso permite que o provedor construa uma árvore de expressão em tempo de execução e a traduza para uma consulta nativa otimizada.

// No Entity Framework, 'context.Livros' é IQueryable<Livro>
var consultaEF = context.Livros.Where(l => l.AnoPublicacao > 2000).OrderBy(l => l.Titulo);
// Esta consulta é traduzida para SQL e executada no servidor do banco de dados.

Tags: linq C# .NET Consultas PLINQ

Publicado em 7-17 16:50