1. Introdução
Às vezes a gente precisa trabalhar com duas coleções diferentes que estão ligadas por algum campo em comum. Por exemplo, temos uma lista de pedidos e outra coleção de clientes. Como saber qual pedido é de qual cliente? Pra isso, as duas coleções precisam estar ligadas por um identificador comum — tipo o userId.
Em bancos de dados relacionais, essa tarefa é resolvida com a operação JOIN, que junta linhas de tabelas diferentes quando as chaves batem. No LINQ tem um operador igualzinho — também chamado de Join.
Imagina duas tabelas: na primeira — lista de leitores da biblioteca com seus IDs, e na segunda — lista de pedidos de livros, onde cada pedido tem o id do leitor. Com o join a gente "cola" essas tabelas pelo id, e no final sai um par “leitor + pedido dele”.
Aliás, se você achava que join era só pra "banco de dados", tá perdendo coisa! No mundo da programação, juntar coleções é super comum, principalmente quando mexe com sistemas externos ou arquivos estruturados (JSON, XML e aquelas planilhas do Excel da contabilidade).
2. Assinatura do método Join e como funciona
Olha só como é o método Join:
public static IEnumerable<TResult> Join<TOuter, TInner, TKey, TResult>(
this IEnumerable<TOuter> outer, // primeira coleção (externa)
IEnumerable<TInner> inner, // segunda coleção (interna)
Func<TOuter, TKey> outerKeySelector, // como pegar a chave do elemento externo
Func<TInner, TKey> innerKeySelector, // como pegar a chave do elemento interno
Func<TOuter, TInner, TResult> resultSelector) // função pra gerar o resultado (novo elemento)
De cara pode parecer complicado, mas calma que a gente vai destrinchar.
Como funciona:
Pra cada elemento da primeira coleção (outer), o LINQ procura os elementos da segunda (inner) que têm a mesma chave. Quando as chaves batem, chama o resultSelector, e o resultado vai pra coleção final.
3. Exemplo: Juntando clientes e seus pedidos
Vamos criar duas classes e duas coleções. Essa ideia é tipo uma continuação do nosso app de exemplo (imagina uma lojinha que a gente tá fazendo no curso).
public class Customer
{
public int Id { get; set; }
public string Name { get; set; } = "";
}
public class Order
{
public int Id { get; set; }
public int CustomerId { get; set; }
public string Product { get; set; } = "";
}
// Coleção de clientes
var customers = new List<Customer>
{
new Customer { Id = 1, Name = "Vasya" },
new Customer { Id = 2, Name = "Petya" },
new Customer { Id = 3, Name = "Masha" },
};
// Coleção de pedidos
var orders = new List<Order>
{
new Order { Id = 101, CustomerId = 2, Product = "Livro" },
new Order { Id = 102, CustomerId = 1, Product = "Caneta" },
new Order { Id = 103, CustomerId = 2, Product = "Caderno" },
new Order { Id = 104, CustomerId = 3, Product = "Borracha" },
};
Agora a gente quer pegar todos os clientes com seus pedidos. Tipo mostrar: "Petya pediu Livro", "Petya pediu Caderno" e por aí vai.
Usando o Join
// Juntando clientes com pedidos por customer.Id e order.CustomerId
var query = customers.Join(
orders,
customer => customer.Id, // Como pegar a chave do cliente
order => order.CustomerId, // Como pegar a chave do pedido
(customer, order) => new // O que fazer com os pares que batem (cria novo objeto)
{
CustomerName = customer.Name,
Product = order.Product
}
);
// Mostrando o resultado
foreach (var item in query)
{
Console.WriteLine($"{item.CustomerName} pediu {item.Product}");
}
O que vai aparecer:
Vasya pediu Caneta
Petya pediu Livro
Petya pediu Caderno
Masha pediu Borracha
Repara: se o cliente tem vários pedidos, ele aparece várias vezes — um pra cada pedido. É assim mesmo que tem que ser!
4. Tabela: Comparando Join e GroupBy + SelectMany
| Operação | Resultado | Quando usar |
|---|---|---|
|
Lista simples de pares | JOIN clássico do SQL. Cada par (match) é uma linha separada. |
|
Grupos com subcoleções | Quer pegar "cliente + todos os pedidos dele" no formato "um pra muitos". |
Quem tá começando costuma usar Join quando quer uma estrutura “cliente → lista de pedidos”. Mas o Join funciona linha a linha e não agrupa os dados. Pra isso tem o GroupJoin — que a gente vai ver na próxima aula. Ou então usar GroupBy.
5. Join na Query Syntax (estilo SQL do LINQ)
O LINQ aceita dois jeitos de escrever: encadeando métodos (Method Syntax, tipo Join(...)) e o tal do estilo SQL (Query Syntax), que parece um SELECT de banco de dados.
Pra muita gente — principalmente quem já mexeu com banco — esse jeito é mais fácil de entender:
var query2 =
from customer in customers
join order in orders
on customer.Id equals order.CustomerId
select new
{
CustomerName = customer.Name,
Product = order.Product
};
foreach (var item in query2)
{
Console.WriteLine($"{item.CustomerName} pediu {item.Product}");
}
Fica ligado:
Na Query Syntax tem que usar a palavra equals — o operador == não funciona aqui!
Essa é uma pegadinha clássica em entrevista, principalmente pra quem tá começando 😉
6. Detalhes importantes e pegadinhas
Às vezes acontece de nem todos os elementos das coleções irem pro resultado final. Isso é porque o método Join faz o tal do “inner join”, ou seja, só junta os elementos que têm chaves iguais. Se um cliente não tem pedido, ele nem aparece no resultado. E se tem um pedido com CustomerId que não existe nos clientes, esse pedido também fica de fora.
Mas e se você quiser pegar todos os clientes, até os que não têm pedido? Pra isso existe o “left join”, que no LINQ rola usando GroupJoin junto com SelectMany. Isso a gente vê na próxima aula. No Join clássico, os dois lados têm que existir — senão não bate e o elemento fica de fora.
7. Trabalhando com várias chaves (composite key)
Às vezes você precisa juntar coleções não só por um campo, mas por vários. Tipo: juntar produtos e vendas por "Código do produto + Ano da venda".
No LINQ, isso se resolve criando objetos anônimos como chave:
var sales = ...; // vendas
var products = ...; // produtos
var query = products.Join(
sales,
prod => new { prod.Code, prod.Year },
sale => new { sale.ProductCode, sale.Year },
(prod, sale) => new { prod.Name, sale.Amount }
);
Tem que garantir que os tipos e nomes das propriedades nesses objetos anônimos sejam iguais. Se forem diferentes, não vai bater nada — mesmo que os valores sejam iguais.
8. Como fica a coleção juntada — esquema
Olha um esquema mostrando como funciona o join (bem simplificado):
flowchart LR
subgraph Customers
A1["Vasya (Id=1)"]
A2["Petya (Id=2)"]
A3["Masha (Id=3)"]
end
subgraph Orders
B1["Caneta (CustomerId=1)"]
B2["Livro (CustomerId=2)"]
B3["Caderno (CustomerId=2)"]
B4["Borracha (CustomerId=3)"]
end
A1-->|Id=1|B1
A2-->|Id=2|B2
A2-->|Id=2|B3
A3-->|Id=3|B4
Dá pra ver no esquema que cada cliente liga com todos os pedidos dele — desde que as chaves batam.
9. Rapidinho sobre erros e pegadinhas
Um erro muito comum é trocar a ordem dos parâmetros, principalmente se o tipo das chaves nas duas coleções for igual. O compilador ou o LINQ não vão reclamar, mas o resultado pode sair vazio ou estranho.
Outra armadilha clássica é tentar fazer um “left join” usando só o Join, achando que vai aparecer todos os clientes, até os sem pedido. Mas o Join clássico só junta pares que batem, e não inclui quem não tem correspondente.
GO TO FULL VERSION