Operador 'Join' do LINQ: Um Tutorial Completo
NOTA: Eu escrevi este artigo, originalmente, para o blog da Stackify. Caso lhe interesse, você pode ver o artigo original, em inglês, no site deles.
Acho que a maioria dos desenvolvedores C# concordaria que o LINQ é parte essencial da experiência de escrever código com a linguagem. O LINQ oferece uma maneira fluente, intuitiva e consistente de consultar conjuntos de dados. Neste artigo, ajudaremos você a dominar o LINQ, abordando o operador join
.
Vou abrir o post com uma definição do próprio LINQ, para que estejamos todos na mesma página. Depois disso, você verá uma explicação das operações de join no LINQ. Depois, é hora de arregaçar as mangas e colocar a mão na massa com nosso guia prático sobre o operador de join
.
Vamos começar.
O Quê é LINQ?
LINQ significa Language Integrated Query. É um recurso do C# que oferece uma sintaxe única e consistente para consultar conjuntos de dados, independentemente da origem deles. A principal vantagem do LINQ é que você pode usar a mesma sintaxe para consultar dados na memória, em um banco de dados, em arquivos XML e assim por diante.
O LINQ está disponível em dois “sabores” diferentes: a sintaxe de consulta (query syntax) e a sintaxe de método (method syntax).
A sintaxe de consulta utiliza palavras-chave especiais para criar uma sintaxe que é familiar a qualquer pessoa que tenha trabalhado com SQL. Aqui está um exemplo que consulta uma sequência de números, filtrando os maiores que 5:
A syntaxe de método usa métodos de extensão para realizar a mesma consulta:
O que é o operador LINQ Join?
Ao trabalhar com dados, um cenário comum é ter duas fontes de dados que você deseja combinar com base em alguns critérios. Por exemplo, você pode ter uma tabela Books
(Livros) e uma tabela Authors
(Autores) em seu banco de dados, com um relacionamento de um para muitos entre elas, ou seja, um autor pode ser autor de muitos livros, mas cada livro tem apenas um autor. Se você precisar compilar uma lista de livros que contenha o nome do autor, precisará executar uma junção para fazer a correspondência entre cada linha da tabela Books
e sua contraparte na tabela Authors
.
Uma junção no LINQ é essencialmente a mesma coisa: uma operação em que você pode mesclar duas coleções de acordo com alguns critérios que você define.
O operador LINQ Join na prática
Os exemplos sempre deixam as coisas mais claras. Então, vamos ver como usar o operador join
na prática.
Iniciando com um problema
Digamos que você tenha um aplicativo de comércio eletrônico com alguns dados sobre categorias:
Id | Name |
---|---|
1 | Electronics |
4 | Toys |
5 | Stationery |
7 | Books |
10 | Clothes |
Agora, vamos ver alguns produtos:
Id | Name | Category_Id |
---|---|---|
1 | Amazon Kindle | 1 |
2 | Refactoring | 7 |
3 | C# in Depth | 7 |
4 | Legal Pad 50 sheets | 5 |
Você já entendeu aonde isso vai, né? A próxima coisa que você deseja fazer é produzir uma única coleção, com a lista de produtos e os nomes das categorias às quais eles pertencem. Em outras palavras, uma visualização como esta:
Id | Name | Category |
---|---|---|
1 | Amazon Kindle | Electronics |
2 | Refactoring | Books |
3 | C# in Depth | Books |
4 | Legal Pad 50 sheets | Stationery |
Resolvendo o problema: executando um LINQ Inner Join
Como seria essa operação no código? Em primeiro lugar, precisamos de código para representar nossas categorias e produtos. Graças ao recurso de record
do C#, duas linhas de código são suficientes para isso:
Agora, vamos ter uma lista de cada tipo:
Como você pode ver, a lista de produtos tem um produto adicional (luvas cirúrgicas) cujo ID de categoria não corresponde a nenhuma das categorias disponíveis. Tenha isso em mente, pois será relevante em um momento.
Agora, vamos escrever o código para realizar essa união. Vou mostrar o código de uma só vez e depois eu explico:
Agora, a explicação:
from p in products -> estamos definindo a origem de uma de nossas fontes de dados
- join c in categories -> Aqui, estamos dizendo que queremos juntar a coleção anterior com esta *on p.CategoryId equals c.Id** -> Essa é a condição para a união: o CategoryId em cada produto deve corresponder ao Id de uma categoria
- select new… -> Aqui, estamos aproveitando o recurso de objetos anônimos do C# para criar um novo objeto em tempo real, que tem as propriedades que desejamos
O resultado dessa consulta é um IEnumerable
do nosso objeto anônimo. Em seguida, iteramos por cada item dessa coleção, exibindo-o no console. Este é o resultado:
`
{ Id = 1, Name = Amazon Kindle, Category = Electronics }
{ Id = 2, Name = Refactoring, Category = Books }
{ Id = 3, Name = C# In Depth, Category = Books }
{ Id = 4, Name = Legal Pad 50 Sheets, Category = Stationery }
Aqueles que se lembram das aulas de banco de dados perceberão que a junção LINQ que realizamos é equivalente a uma junção interna (inner join) no SQL. Em outras palavras, somente os itens que têm uma correspondência são retornados. Em SQL, a consulta equivalente seria a seguinte:
Executando uma junção externa (OUTER JOIN) no LINQ
E se você quisesse executar o equivalente a um OUTER JOIN
do SQL? Ou seja, você deseja recuperar todos os produtos, mesmo aqueles que não correspondem a nenhuma categoria. Como fazer isso?
Aqui está a consulta atualizada:
Parece semelhante, mas há duas diferenças:
- on p.CategoryId equals c.Id into joinedCategories -> aqui, depois de unir produtos com categorias, enviamos o resultado, como uma sequência agrupada, para a variável de intervalo joinedCategories
- from c in joinedCategories.DefaultIfEmpty() -> Em seguida, recuperamos os itens da groupedSequence, usando o método DefaultIfEmpty() para retornar o valor padrão quando nenhuma correspondência for encontrada
- Category = c?.Name -> Por fim, ao atribuir o nome da categoria à propriedade Category do nosso objeto anônimo, precisamos usar o operador condicional nulo para evitar uma exceção de referência nula (já que o valor padrão de Category é nulo porque é um tipo de referência).
O resultado agora é diferente:
Como você pode ver, o produto “Surgical Gloves” (Luvas cirúrgicas) agora aparece, mesmo que não tenha uma categoria correspondente.
Inner Join do LINQ com a condição Where
Realizar uma junção com uma cláusula where
é muito fácil. Neste exemplo, realizaremos uma junção interna, filtrando apenas os produtos cujo id seja igual ou maior que 3:
Inner Join do LINQ com várias condições
Se você quiser usar várias condições em sua junção, basta usar mais de uma cláusula where. Vamos atualizar nossa consulta mais uma vez:
Aqui, estamos filtrando apenas as categorias cujos nomes terminam com a letra “s”.`
LINQ Join com chave composta
Até agora, todos os nossos exemplos usaram chaves únicas para fazer a correspondência. Você também pode usar chaves compostas, ou seja, mais de um valor, para fazer a correspondência.
Suponha que as nossas classes Product
e Category
tenham adquirido uma nova propriedade chamada Status
, que é um enum que pode variar entre três estados: Pending
(pendente), Active
(ativo) e Archived
(arquivado). Agora, a propriedade Status também precisa ser usada para a correspondência.
Todos os nossos produtos estão ativos, mas nem todas as categorias:
Fica assim a consulta atualizada:
Não é muito mais complicado do que antes. A diferença é que, agora, usamos um objeto anônimo para realizar a comparação usando as propriedades id e status.
Um único resultado é exibido a partir dessa consulta:
Conclusão
Como vimos, o LINQ é uma parte essencial do trabalho com o C#. Você pode aproveitar o LINQ em muitos cenários diferentes, desde o trabalho com dados na memória até XML e SQL. Você pode usar o LINQ em ORMs, como NHibernate e Entity Framework.
As equipes que desejam tornar suas experiências com LINQ ainda melhores podem usar as ferramentas à sua disposição. Por exemplo, Stackify’s Prefix e Retrace oferecem recursos avançados de rastreamento, criação de perfil e registro centralizado que ajudam as equipes a inspecionar seu código para encontrar oportunidades de melhorias de desempenho, o que inclui consultas LINQ.
Encontrou algum erro no post? Sugira uma edição ← Git Bisect: Uma Introdução Para Iniciantes Git Criar Branch: 4 Maneiras Diferentes →