O que é LINQ
LINQ é a abreviação de Language Integrated Query (Consulta Integrada à Linguagem), sendo uma inovação revolucionária introduzida no Visual Studio 2008 e .NET Framework 3.5. Esta tecnologia estabelece uma ponte entre o mundo dos objetos e o mundo dos dados.
O LINQ oferece suporte a diversas fontes de dados:
- ADO.NET DataSet
- Documentos XML
- Bancos de dados SQL Server
- Qualquer coleção de objetos que implemente a interface IEnumerable ou IEnumerable(T) genérica
- E muito mais...
Vantagens do LINQ
Consulta SQL tradicional
select FirstName,LastName,* from Customers
where city = 'Shanghai'
order by district
Trata-se de uma simples representação em formato de string, sem verificação de tipos em tempo de compilação e sem suporte de IntelliSense do IDE.
O exemplo anterior é específico para SQL. Para diferentes fontes de dados, como documentos XML, serviços web diversos, seria necessário aprender métodos de consulta distintos.
Exemplo de consulta LINQ
Oferece verificação de tipos completa e suporte de IntelliSense do IDE.
Etapas de uma Consulta LINQ
Todas as operações de consulta LINQ consistem em três etapas distintas:
- Obter a fonte de dados
- Criar a consulta
- Executar a consulta
Fonte de dados
Para utilizar o LINQ em consultas, a fonte de dados deve suportar a interface IEnumerable ou IEnumerable(T) genérica ou interfaces derivadas como IQueryable(T).
Consulta
A consulta especifica quais informações serão recuperadas da fonte de dados. A consulta também pode determinar como essas informações serão ordenadas, agrupadas e estruturadas antes de serem retornadas. As consultas são armazenadas em variáveis de consulta e inicializadas através de expressões de consulta. Para facilitar a escrita de consultas, o C# introduziu uma nova sintaxe de consulta.
Execução da consulta
A variável de consulta apenas armazena os comandos da consulta. A execução real da consulta é adiada até o momento em que ocorre a iteração sobre a variável em uma instrução foreach. Este conceito é conhecido como "execução adiada" (deferred execution).
Para forçar a execução imediata, existem duas abordagens:
- Utilizar funções de agregação (Count, Max, Average, First)
- Chamar os métodos ToList ou ToArray para armazenar os resultados em cache
Operações Básicas de Consulta
Cláusula from
Utilizada para obter a fonte de dados:
var consultaTodosClientes =
from cliente in Clientes
select cliente;
- A expressão de consulta deve iniciar com uma cláusula from
- No exemplo,
clienteé uma variável de intervalo, semelhante à variável de iteração em um loop foreach, porém na expressão de consulta a iteração não ocorre efetivamente. Quando a consulta é executada, a variável de intervalo serve como referência a cada elemento subsequente em Clientes. Como o compilador consegue inferir o tipo decliente, não é necessário especificar o tipo explicitamente. - Clientes é a fonte de dados que implementa IEnumerable ou IEnumerable(T) ou suas interfaces derivadas.
Cláusula from composta
Em determinadas situações, cada elemento da sequência original pode ser, ele próprio, uma sequência ou conter sequências. Utilizada para acessar coleções internas de uma fonte de dados.
Requisitos: Identificar estudantes com notas superiores a 90 pontos, obtendo seus nomes e notas.
//Fonte de dados
IList<Estudante> estudantes = new List<Estudante>
{
new Estudante{ Nome="Carlos", Notas=new List<int>{89,93,88,78}},
new Estudante{ Nome="Marcelo",Notas=new List<int>{92,87,83,91}},
new Estudante{ Nome="Fernanda",Notas=new List<int>{53,76,72,62}}
};
//Comando de consulta usando cláusula from composta
var obterEstudantes =
from est in estudantes
from nota in est.Notas
where nota > 90
select new { Nome=est.Nome,Nota=nota};
Aálise: Observa-se que cada objeto Estudante possui uma propriedade Notas, que é uma coleção do tipo List. Neste caso, utiliza-se a cláusula from composta para a consulta. Primeiramente, itera-se sobre cada objeto na coleção de estudantes, e então, através de outra cláusula from, itera-se sobre a propriedade Notas de cada estudante, filtrando as informações dos estudantes com notas superiores a 90.
Cláusula let para expandir variáveis de intervalo
Utilizada para criar variáveis de intervalo próprias para a consulta.
Requisitos: Extrair e imprimir no console todas as palavras iniciadas por vogais de duas frases em inglês presentes em um array de strings.
string[] frases ={
"I am a new Student.",
"You are a talent"
};
var consulta = from sentenca in frases
let palavras = sentenca.Split(' ')
from palavra in palavras
let p = palavra.ToLower()
where p[0] == 'a' || p[0] == 'e' || p[0] == 'i' || p[0] == 'o' ||
p[0] == 'u'
select palavra;
foreach (var palavra in consulta)
{
Console.Write(palavra + ",");
}
Análise: Inicialmente, itera-se sobre cada string no array de frases. A cláusula let cria uma variável de intervalo própria chamada palavras, invokedo o método Split(' ') para dividir cada string em palavras separadas por espaços e armazená-las na variável palavras. Em seguida, utiliza-se novamente a cláusula let para criar outra variável de intervalo própria chamada palavra, aplicando o método ToLower() para converter cada palavra para minúsculas. Por fim, filtra-se as palavras cuja primeira letra é uma vogal.
Observação: A instrução let serve para renomear. Ela está posicionada entre a primeira cláusula from e a instrução select.
Este exemplo projeta a expressão "Let" final a partir de uma junção:
var consulta =
from c in db.Clientes
join p in db.Pedidos on c.ClienteID
equals p.ClienteID into listaPedidos
let cidadeCompleta = c.Cidade + c.Pais
from p in listaPedidos
select new
{
c.NomeContato,
p.PedidoID,
cidadeCompleta
};
Cláusula where
Aplica uma condição booleana (predicado) a cada elemento de origem (referenciado pela variável de intervalo) e retorna os elementos que satisfazem a condição especificada.
Cenários de uso: Implementar filtragem e consultas.
Observação: Assim como o comando Where em SQL, esta cláusula serve para limitar o escopo, ou seja, filtrar. A condição é especificada na subcláusula subsequente.
As operações Where incluem três formas: simples, com condições relacionais e forma First().
1) Forma simples
Exemplo: Filtrar clientes que residem em São Paulo
var consulta =
from c in db.Clientes
where c.Cidade == "São Paulo"
select c;
Outro exemplo: Filtrar funcionários contratados a partir de 2020:
var consulta =
from f in db.Funcionarios
where f.DataContratacao >= new DateTime(2020, 1, 1)
select f;
2) Forma com condições relacionais
Filtrar produtos com estoque abaixo do ponto de reposição e que não foram descontinuados:
var consulta =
from p in db.Produtos
where p.UnidadesEstoque <= p.PontoReposicao && !p.Descontinuado
select p;
Filtrar produtos com preço unitário maior que 50 ou que já foram descontinuados:
var consulta =
from p in db.Produtos
where p.PrecoUnitario > 50m || p.Descontinuado
select p;
O exemplo abaixo chama o método where duas vezes para filtrar produtos com preço maior que 50 e descontinuados:
var consulta =
db.Produtos.Where(p => p.PrecoUnitario > 50m).Where(p => p.Descontinuado);
3) Forma First()
Retorna um elemento da coleção,实质上相当于 na instrução SQL adicionar TOP (1).
Uso simples: Selecionar o primeiro transportador da tabela:
Transportador transportador = db.Transportadores.First();
Elemento: Selecionar um único cliente com ID "BONAP":
Cliente cliente = db.Clientes.First(c => c.ClienteID == "BONAP");
Condição: Selecionar um pedido com frete maior que 100,00:
Pedido pedido = db.Pedidos.First(p => p.Frete > 100.00M);
Outros exemplos de código
Requisitos: Consultar números pares menores que 5 de um array e imprimi-los no console
//Fonte de dados
int[] numeros = { 0, 3, 2, 1, 9, 6, 8, 7, 4, 5 };
//Expressão de consulta usando cláusula where
var consulta = from num in numeros
where num < 5 && num % 2 == 0
select num;
//Executar consulta
foreach (var num in consulta)
{
Console.WriteLine(num);
}
Análise: Inicialmente, itera-se sobre cada elemento do array. Em seguida, a instrução where filtra os números menores que 5 e cujo resto da divisão por 2 é igual a 0.
A cláusula where não apenas pode usar expressões para filtrar, mas também pode utilizar métodos:
public static bool ÉPar(int numero)
{
return numero % 2 == 0 ? true : false;
}
//Fonte de dados
int[] numeros = { 0, 3, 2, 1, 9, 6, 8, 7, 4, 5 };
//A cláusula where também pode aceitar um método
var consulta = from num in numeros
where ÉPar(num)
select num;
foreach (var num in consulta)
{
Console.Write(num + ",");
}
Este é um exemplo de uso de método na cláusula where, produzindo o mesmo resultado do exemplo anterior.
Observações importantes sobre o uso da cláusula where
- Uma expressão de consulta pode conter várias cláusulas where
- A cláusula where é um mecanismo de filtragem. Embora não possa ser a primeira ou última cláusula, pode ser posicionada em praticamente qualquer lugar na expressão de consulta. A cláusula where pode aparecer antes ou depois da cláusula group, dependendo se a filtragem deve ocorrer antes ou depois do agrupamento dos elementos de origem.
- Se o predicado especificado for inválido para um elemento na fonte de dados, ocorrerá um erro em tempo de compilação. Esta é uma vantagem da verificação de tipos forte oferecida pelo LINQ.
- Em tempo de compilação, a palavra-chave where é convertida em uma chamada ao método operador de consulta padrão where.
Cláusula orderby
Cenários de uso: Ordenar os resultados da consulta, como ordenação por data, etc.
Observação: Ordena a coleção pela expressão especificada; é adiada; por padrão é ordem crescente, adicionando descending indica ordem decrescente; os métodos de extensão correspondentes são OrderBy e OrderByDescending.
- Ordena o conjunto de resultados da consulta em ordem crescente ou decrescente. 1. A ordem de ordenação padrão é crescente.
- Em tempo de compilação, a cláusula orderby é convertida em uma chamada ao método OrderBy. Múltiplas chaves na cláusula orderby são convertidas em chamadas ao método ThenBy. Seguindo o exemplo anterior, este exemplo demonstra ordenação crescente:
var consulta = from num in numeros
where ÉPar(num)
orderby num ascending
select num;
Este exemplo demonstra ordenação decrescente:
var consulta = from num in numeros
where ÉPar(num)
orderby num descending
select num;
1) Forma simples
Este exemplo utiliza orderby para ordenar funcionários por data de contratação:
var consulta =
from f in db.Funcionarios
orderby f.DataContratacao
select f;
Observação: O padrão é ordem crescente.
2) Forma com condição
Observação: A ordem de Where e Order By não é importante. Porém, em T-SQL, Where e Order By têm posições rigorosamente limitadas.
var consulta =
from p in db.Pedidos
where p.CidadeEnvio == "São Paulo"
orderby p.Frete
select p;
Descrição: Utiliza where e orderby para ordenar porfrete.
3) Ordenação decrescente
var consulta =
from p in db.Produtos
orderby p.PrecoUnitario descending
select p;
4) ThenBy
Descrição: Utiliza orderby composto para ordenar clientes:
var consulta =
from c in db.Clientes
orderby c.Cidade, c.NomeContato
select c;
Descrição: Ordena por múltiplas expressões, por exemplo, primeiro pela Cidade, quando as cidades forem iguais, ordena pelo NomeContato. Esta instrução em expressão Lambda é escrita assim:
var consulta =
db.Clientes
.OrderBy(c => c.Cidade)
.ThenBy(c => c.NomeContato).ToList();
Em T-SQL não existe instrução ThenBy, sendo traduzida como OrderBy, podendo também ser expressa como:
var consulta =
db.Clientes
.OrderBy(c => c.NomeContato)
.OrderBy(c => c.Cidade).ToList();
É importante notar que, ao usar múltiplas operações OrderBy, a forma em cascata é reversa. Para ordem decrescente, use os operadores correspondentes.
var consulta =
db.Clientes
.OrderByDescending(c => c.Cidade)
.ThenByDescending(c => c.NomeContato).ToList();
É importante mencionar que a operação OrderBy não suporta ordenação por tipo, nem por classes anônimas. Por exemplo:
var consulta =
db.Clientes
.OrderBy(c => new
{
c.Cidade,
c.NomeContato
}).ToList();
Gerará uma exceção. O erro ocorre porque a operação anterior possui uma classe anônima e, ao usar OrderBy em seguida, a comparação é feita pelos tipos.
var consulta =
db.Clientes
.Select(c => new
{
c.Cidade,
c.Endereco
})
.OrderBy(c => c).ToList();
Caso deseje utilizar OrderBy(c => c), o pré-requisito é que os objetos gerados nas etapas anteriores sejam de tipos primitivos da linguagem C#. Por exemplo, na instrução abaixo, Cidade é do tipo string:
var consulta =
db.Clientes
.Select(c => c.Cidade)
.OrderBy(c => c).ToList();
5) ThenByDescending
Ambos os métodos de extensão são usados após OrderBy/OrderByDescending. O primeiro método de extensão ThenBy/ThenByDescending serve como segundo critério de ordenação, o segundo ThenBy/ThenByDescending como terceiro critério, e assim sucessivamente.
var consulta =
from p in db.Pedidos
where p.FuncionarioID == 1
orderby p.PaisEnvio, p.Frete descending
select p;
Descrição: Utiliza orderby para ordenar primeiro por país de destino e depois porfrete do maior para o menor, para os pedidos do FuncionárioID 1.
6) Forma com GroupBy
var consulta =
from p in db.Produtos
group p by p.CategoriaID into grupo
orderby grupo.Chave
select new {
grupo.Chave,
ProdutosMaisCaros =
from p2 in grupo
where p2.PrecoUnitario == grupo.Max(p3 => p3.PrecoUnitario)
select p2
};
Descrição: Utiliza orderby, Max e Group By para obter o produto mais caro de cada categoria, ordenando este grupo de produtos por CategoriaID.
Cláusula group
- A cláusula group retorna uma sequência de objetos IGrouping(Chave, Elemento)
- Em tempo de compilação, a cláusula group é convertida em uma chamada ao método GroupBy
Requisitos: Agrupar pela primeira letra e imprimir no console
//Fonte de dados
string[] frutas = { "maçã", "banana", "pêssego", "laranja", "melão", "limão" };
//Expressão de consulta para agrupamento
var consulta = from f in frutas
group f by f[0];
//Executar consulta
foreach (var letras in consulta)
{
Console.WriteLine("palavras que começam com a letra:" + letras.Chave);
foreach (var palavra in letras)
{
Console.WriteLine(palavra);
}
}
Análise: Inicialmente, itera-se sobre cada string no array de strings. Em seguida, agrupa-se pela primeira letra de cada string, retornando o resultado.
var consulta = from f in frutas
group f by f[0] into g
where g.Chave == 'p' || g.Chave == 'b'
select g;
Caso deseje executar operações adicionais de consulta em cada grupo, pode-se utilizar a palavra-chave contextual into para especificar um identificador temporário. Ao usar into, deve-se continuar escrevendo a consulta e, eventualmente, terminá-la com uma instrução select ou outra cláusula group.
Exemplo de agrupamento multi-campo
GroupBy(x => new { x.a , x.b, x.c }).Select( x=> ( new NomeClasse { a=x.Chave.a , b=x.Chave.b , c = x.Chave.c } ))
Cláusula join
- A cláusula join pode associar elementos de diferentes sequências de origem que não possuem relação direta no modelo de objetos.
- O único requisito é que os elementos de cada fonte compartilhem algum valor que possa ser comparado para verificar igualdade.
- A cláusula join utiliza a palavra-chave especial equals para comparar se as chaves especificadas são iguais.
1) Junção interna
var consultaJuncaoInterna =
from categoria in categorias
join produto in produtos on categoria.ID equals produto.CategoriaID
select new { NomeProduto = produto.Nome, Categoria = categoria.Nome };
2) Junção agrupada
var consultaJuncaoAgrupada =
from categoria in categorias
join produto in produtos on categoria.ID equals produto.CategoriaID
into grupoProdutos
select new { NomeCategoria = categoria.Nome, Produtos = grupoProdutos };
3) Junção externa esquerda
var consultaJuncaoExterna =
from categoria in categorias
join produto in produtos on categoria.ID equals produto.CategoriaID
into grupoProdutos
from item in grupoProdutos.DefaultIfEmpty(new Produto{Nome =
string.Empty, CategoriaID = 0})
select new { NomeCat = categoria.Nome, NomeProd = item.Nome };
Na junção externa esquerda, todos os elementos da sequência de origem esquerda serão retornados, mesmo que não possuam elementos correspondentes na sequência direita.
Para executar uma junção externa esquerda no LINQ, deve-se combinar o método DefaultIfEmpty com a junção agrupada para especificar o elemento direito padrão a ser gerado quando um elemento não possuir correspondência. Pode-se utilizar null como valor padrão para qualquer tipo de referência. Também é possível especificar um tipo padrão definido pelo usuário.
Palavra-chave equals
- A cláusula join executa junção por igualdade. Em outras palavras, o pareamento é baseado apenas na relação de igualdade entre duas chaves.
- Para indicar que todas as junções são por igualdade, a cláusula join utiliza a palavra-chave equals em vez do operador ==
Cláusula select (seleção e projeção)
A cláusula select pode especificar o tipo de valor que será produzido quando a consulta for executada. O resultado desta cláusula é baseado no cálculo de todas as cláusulas anteriores e de todas as expressões presentes na própria cláusula select.
A expressão de consulta deve terminar com uma cláusula select ou group.
No caso mais simples, a cláusula select apenas especifica a variável de intervalo. Isso faz com que a sequência retornada contenha elementos do mesmo tipo da fonte de dados.