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:
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:
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 }; int 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:
Super DVD Vídeo Aulas - Vídeo Aula sobre VB .NET, ASP .NET e C#
C# - O tipo de dados Dynamic - Apresentando o tipo dynamic; propriedades e recursos
C# - 10 dicas para otimizar o seu código - Aumente o desempenho e eficácia de sua aplicação C#