LINQ - Consultas com Expressões Lambdas


As consultas com expressões lambdas são muito flexíveis e de fundamental importância na plataforma .NET.

Por isso vamos revisar conceitos básicos da LINQ relacionados com os operadores de consultas padrão efetuadas com os recursos da LINQ.

A unidade básica de dados da LINQ são sequências e elementos onde :

No exemplo a seguir times é uma sequência e Palmeiras, Santos, Botofago e Vasco são elementos da sequência:

string[] times = { "Palmeiras", "Santos", "Botafogo", "Vasco" };

Geralmente a sequência representa uma coleção de objetos na memória.

Um operador de consulta é um método que transforma uma seqüência. Um típico operador de consulta aceita uma sequencia de entrada e emite uma seqüência de saída transformada.

Na classe Enumerable do namespace System.Linq, existem cerca de 40 operadores de consulta, todos implementados como métodos de extensão estáticos , chamado de operadores padrão de consultas.

A LINQ também suporta seqüências que podem ser alimentadas dinamicamente a partir de uma fonte de dados remota, como um servidor SQL. Essas seqüências implementam a interface IQueryable<> e são suportadas por meio de um conjunto de operadores padrão de consultas da classe Queryable.

Uma consulta é uma expressão que transforma seqüências com operadores de consulta. A mais simples consulta compreende uma sequencia de entrada e um operador. Por exemplo, podemos aplicar o o operador Where em uma matriz simples para extrair elementos com tamanho maior que seis caracteres da seguinte forma:

using System;
using System.Collections.Generic;
using System.Linq;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            string[] times = { "Palmeiras", "Santos", "Botafogo", "Vasco" };

            IEnumerable<string> timesFiltrados = Enumerable.Where( times, n => n.Length > 6);

            foreach (string n in timesFiltrados)
                Console.Write(n + "|");

            Console.ReadKey();
        }
    }
}

Como os operadores de consulta padrão são implementados como métodos de extensão, podemos chamar o operador Where diretamente em sobre tiems como se fosse um método de instância da seguinte forma:

IEnumerable<string> timesFiltrados = times.Where(n => n.Length >= 6);

Para criar consultas mais complexas, você adiciona operadores de consulta adicionais criando uma cadeia. Por exemplo, a seguinte consulta extrai todas as strings contendo a letra  's' , ordena pelo tamanho e então converte o resultado para caixa alta:

    public static void consulta2()
        {
            string[] times = { "Palmeiras", "Santos", "Botafogo", "Vasco" };

            IEnumerable<string> consulta = times
                                            .Where (n => n.Contains ("s"))
                                            .OrderBy (n => n.Length)
                                            .Select (n => n.ToUpper( ));

            foreach (string n in consulta)
                Console.Write(n + " | ");
        }

Os operadores padrão de consulta Where, OrderBy e Select são usados em métodos de extensão na classe Enumerable.

O fluxo ocorre da esquerda para a direita e dessa forma os elementos são primeiro filtrados, depois ordenados e finalmente transformados.

A seguir temos as assinaturas de cada um dos métodos de extensão:

  1. static IEnumerable<TSource> Where<TSource> (this IEnumerable<TSource> source,Func<TSource,bool> predicate)
  2. static IEnumerable<TSource> OrderBy<TSource,TKey> (this IEnumerable<TSource> source,Func<TSource,TKey> keySelector)
  3. static IEnumerable<TResult> Select<TSource,TResult> (this IEnumerable<TSource> source,Func<TSource,TResult> selector)

Quando os operadores de consulta são encadeados como neste exemplo, a seqüência de saída de um operador é a seqüência de entrada do próximo.

O resultado final se assemelha a uma linha de produção conforme ilustra a figura abaixo:

var filtro = times.Where (n => n.Contains ("a"));
var ordenacao = filtro.OrderBy (n => n.Length);
var consultaFinal = ordenacao.Select (n => n.ToUpper( ));

Obs: Uma expressão retornando um valor boleano é chamada de um predicado (predicate).

O objetivo da expressão lambda depende do operador de consulta. O operador Where, indica se um elemento deve ser incluído na sequencia de saída. No caso do operador OrderBy, a expressão lambda mapeia cada elemento na seqüência de entrada para a sua chave de classificação. Com o operador Select, a expressão lambda determina como cada elemento na seqüência de entrada é transformado antes de ser alimentado com a seqüência de saída

Os operadores padrão de consulta utilizam delegates Func genéricos, onde Func é uma família de delegates genéricos de uso geral presentes no namespace System.Linq, definidos com o seguinte objetivo: Os argumentos de tipos em Func aparecem na mesma ordem em que são usados nas expressões lambdas.

Assim, Func <TSource,bool> corresponde à expressão lambda : TSource => bool , onde aceita um argumento TSource e retorna um valor bool.

Da mesma forma , Func<TSource,TResult> corresponde a expressão lambda TSource=> TResult

A seguir temos todas as definições para Func delegate:

  1. delegate TResult Func <T> ( );
  2. delegate TResult Func <T, TResult> (T arg1);
  3. delegate TResult Func <T1, T2, TResult> (T1 arg1, T2 arg2);
  4. delegate TResult Func <T1, T2, T3, TResult> (T1 arg1, T2 arg2, T3 arg3);
  5. delegate TResult Func <T1, T2, T3, T4, TResult> (T1 arg1, T2 arg2, T3 arg3, T4 arg4);

Os operadores padrão de consultas utilizam os seguintes nomes de tipos genéricos:

Tipo Genérico Significado
TSource Tipo de elemento para sequência de entrada
TResult Tipo de elemento para sequência de saída - se diferente de TSource
TKey Tipo de elemento para a key usada para ordenação, agrupamento e junção

- TSource é determinada pela seqüência de entrada.
- TResult e TKey são inferidos a partir de sua expressão lambda.

Por exemplo, considere a assinatura do operador de consulta Select:

static IEnumerable<TResult> Select<TSource,TResult> (this IEnumerable<TSource> source,Func<TSource,TResult> selector)

Func <TSource,TResult> corresponde a uma expressão lambda TSource => TResult que mapeia um elemento de entrada a um elemento de saída.

TSource e TResult são tipos diferentes, de modo que a expressão lambda pode alterar o tipo de cada elemento. Além disso, a expressão lambda determina o tipo de seqüência de saída. A consulta a seguir usa Select para transformar elementos do tipo string para elementos do tipo int:

string[] times = { "Palmeiras", "Santos", "Botafogo", "Vasco" };

IEnumerable<int> consulta = times.Select (n => n.Length);

foreach (int n in consulta)
       Console.Write(n);

A sequência de saída são números inteiros representando o tamanho de cada um dos nomes dos times definidos.

Compilador infere o tipo de TResult a partir do valor de retorno da expressão lambda. Neste exemplo TResult é inferido como sendo do tipo int.

Ordenação Natural

A ordem original dos elementos dentro de uma seqüência de entrada é significativa nas consultas LINQ. Alguns operadores de consulta, como Take,Skip, e Reverse, se baseiam esse comportamento.

             int[] numeros = { 10, 9, 8, 7, 6 , 2, 4};
            
            var primeiros_Tres_Numeros = numeros.Take(3);

            Console.WriteLine("Os 3 primeiros números da sequência são { 10, 9, 8, 7, 6 , 2, 4} :");

            foreach (var n in primeiros_Tres_Numeros) 
            { Console.WriteLine(n); }

             int[] numeros = { 10, 9, 8, 7, 6 };
            
            var Todos_Numeros_Exceto_Os_3_Primeiros = numeros.Skip(3);

           Console.WriteLine("Listando todos, exceto os 3 primeiros números : { 10, 9, 8, 7, 6 } :");

            foreach (var n in Todos_Numeros_Exceto_Os_3_Primeiros) 
            { Console.WriteLine(n); }

     int[] numeros = { 1, 2, 3 , 4 ,5,  6 };

     var numerosOrdemReversa = numeros.Reverse() ;

     foreach (var n in numerosOrdemReversa)
              { Console.WriteLine(n); }

Operadores como Where e Select preservam a ordem original da seqüência de entrada. (O LINQ preserva a ordem de elementos na seqüência de entrada sempre que possível.)

Nem todos os operadores de consulta retornam uma seqüência. Os operadores de elementos extraem um elemento da seqüência de entrada; Exemplos desses operadores: First, Last, Single e ElementAt

int[] numeros = { 10, 9, 8, 7, 6 };

i
nt primeiro = numeros.First();
int ultimo = numeros.Last();
int segundo = numeros.ElementAt(1);


Console.Write(primeiro);   
//10
Console.Write(ultimo);      
//6
Console.Write(segundo);   
//9

Os operadores de agregação retornam um valor escalar geralmente do tipo numérico. Ex: Count, Min, Max e Sum:

    int[] numeros = { 10, 9, 8, 7, 6 };

    int contador = numeros.Count();
    int minimo = numeros.Min();
    int maximo = numeros.Max();
    int soma = numeros.Sum();
    Console.WriteLine("Total de elementos : " + contador);
    Console.WriteLine("Elemento mínimo    : " + minimo );
    Console.WriteLine("Elemento mãximo    : " + maximo);
    Console.WriteLine("Soma dos elementos : " + soma);

Os operadores quantificadores retornam um valor boleano: Ex: Contains, Any:

            int[] numeros = { 10, 9, 8, 7, 6 };

            bool temONumeroNove = numeros.Contains(9);                    // true
            bool temMaisqueZeroElementos = numeros.Any();                 // true
            bool temUmLementoImpart = numeros.Any(n => n % 2 == 1); // true
            //
            Console.WriteLine("Possui o elemento 9 ? : " + temONumeroNove);
            Console.WriteLine("Possui mais que Zero elementos ? " + temMaisqueZeroElementos);
            Console.WriteLine("Possui um elemento impar ? " + temUmLementoImpart);

Como estes operadores não retornam uma coleção, você não pode realizar uma nova chamada sobre seus resultados. Em outras palavras, eles deve aparecer como o último operador em uma consulta (ou subconsulta).

Alguns operadores de consulta aceitam duas seqüências de entrada. Exemplos:

- Concat, que acrescenta uma seqüência a outra;


  int[] numerosA = { 0, 2, 4 }; 
  int[] numerosB = { 1, 3, 5 }; 

  var todosNumeros = numerosA.Concat(numerosB); 
  
  Console.WriteLine("Todos os numeros de ambos os arrays:"); 

 foreach (var n in todosNumeros) 
     { Console.WriteLine(n); }

- Union que faz o mesmo que Concat mas removendo os elementos em duplicidade;


  int[] numerosA = { 0, 2, 3 }; 
  int[] numerosB = { 1, 3, 5 }; 

  var todosNumerosExcetoDuplicados = numerosA.Union(numerosB); 
  
  Console.WriteLine("Todos os numeros de ambos os arrays \nExceto os duplicados:"); 

 foreach (var n in todosNumerosExcetoDuplicados) 
     { Console.WriteLine(n); }

Os operadores de junção também se enquadram nesta categoria. Ex: Join :

  public static void ExemplodeJoin()
        {
            var clientes = new List<Cliente>() { 
                  new Cliente {Key = 1, Nome = "Macoratti" },
                  new Cliente {Key = 2, Nome = "Miriam" },
                  new Cliente {Key = 5, Nome = "Janice" } 
            };
 
            var pedidos = new List<Pedido>() {
                  new Pedido {Key = 1, NumeroPedido = "Pedido 1" },
                  new Pedido {Key = 1, NumeroPedido = "Pedido 2" },
                  new Pedido {Key = 4, NumeroPedido = "Pedido 3" },
                  new Pedido {Key = 5, NumeroPedido = "Pedido 5" },
            };
 
            var q = from c in clientes
                      join o in pedidos on c.Key equals o.Key
                      select new {c.Nome, o.NumeroPedido};
 
            foreach (var i in q) 
            {
                 Console.WriteLine("Cliente : {0}  Pedido Numero: {1}",i.Nome.PadRight(11, ' '), i.NumeroPedido);
            }
        }
 
        public class Cliente
        {
            public int Key;
            public string Nome;
        }
 
        public class Pedido
        {
            public int Key;
            public string NumeroPedido;
        }
 

 

Apresentei os principais operadores de consulta padrão LINQ; a lista é imensa, mas em outros artigos voltarei ao assunto. Aguarde...

Pegue o projeto completo aqui: LINQ_ConsultasLambdas.zip

"Portanto agora nenhuma condenação há para os que estão em Cristo Jesus, que não andam segundo a carne, mas segundo o espírito." Romanos 8:1 (Edição Revista e Corrigida)

Veja os Destaques e novidades do SUPER DVD Visual Basic (sempre atualizado) : clique e confira !

Quer migrar para o VB .NET ?

Quer aprender C# ??

Quer aprender os conceitos da Programação Orientada a objetos ?

Quer aprender o gerar relatórios com o ReportViewer no VS 2013 ?

  Gostou ?   Compartilhe no Facebook   Compartilhe no Twitter

Referências:


José Carlos Macoratti