CodeGym /Cursos /C# SELF /Juntando coleções com join

Juntando coleções com join ( Join)

C# SELF
Nível 33 , Lição 0
Disponível

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
Join
Lista simples de pares JOIN clássico do SQL. Cada par (match) é uma linha separada.
GroupBy + SelectMany
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
Esquema de ligação entre clientes e pedidos pela chave

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.

Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION