Conteúdo

O Entity Framework Core 10 introduziu uma das melhorias mais aguardadas pela comunidade .NET: os métodos LeftJoin() e RightJoin() como parte integrante da API LINQ oficial do ORM. Pela primeira vez, é possível expressar junções externas de forma direta, legível e com suporte completo a tradução para SQL ANSI-compliant em todos os provedores relacionais suportados.

Esse avanço elimina definitivamente a necessidade de construções verbosas e pouco intuitivas baseadas em GroupJoin + DefaultIfEmpty + SelectMany, que eram a única forma nativa de implementar left e right outer joins no EF Core até a versão 9.0. A mudança não é apenas sintática: ela melhora a manutenibilidade, reduz erros de lógica, facilita a revisão de código e garante geração de SQL mais previsível e otimizada.

Como era antes do .NET 10

Antes do EF Core 10, a única maneira de expressar um left join em LINQ era através da combinação de GroupJoin com DefaultIfEmpty e SelectMany. Essa construção, embora funcional, apresentava múltiplos pontos de fragilidade:

// ANTES: Left Join (clientes com ou sem pedidos) – versão completa com SelectMany
var clientesComPedidos = contexto.Clientes
    .GroupJoin(
        contexto.Pedidos,
        cliente => cliente.Id,
        pedido => pedido.ClienteId,
        (cliente, pedidos) => new { cliente, pedidos })
    .SelectMany(
        x => x.pedidos.DefaultIfEmpty(),
        (cliente, pedido) => new
        {
            cliente.cliente.Nome,
            PedidoId = pedido != null ? pedido.Id : (int?)null,
            DataPedido = pedido != null ? pedido.Data : (DateTime?)null
        });

Problemas evidentes:

  • Legibilidade comprometida: a intenção do join externo não é clara à primeira leitura.
  • Erro comum: esquecer o DefaultIfEmpty() gera um inner join silencioso.
  • Complexidade desnecessária: o uso obrigatório de SelectMany para “desagrupar” o resultado do GroupJoin.
  • SQL gerado imprevisível: em alguns provedores, resultava em subqueries, CROSS APPLY ou APPLY implícito.
  • Dificuldade de manutenção: novos desenvolvedores precisavam aprender um padrão não intuitivo e propenso a erros.

Para right join, a situação era ainda pior: não havia forma nativa. Era necessário inverter a ordem das tabelas e simular com left join, ou recorrer a SQL bruto.

LeftJoin() e RightJoin() nativos

Com o EF Core 10, a sintaxe torna-se direta e alinhada com a semântica SQL:

// DEPOIS: Left Join (clientes com ou sem pedidos)
var resultado = await contexto.Clientes
    .LeftJoin(
        contexto.Pedidos,
        cliente => cliente.Id,
        pedido => pedido.ClienteId,
        (cliente, pedido) => new
        {
            cliente.Nome,
            PedidoId = pedido != null ? pedido.Id : (int?)null,
            DataPedido = pedido != null ? pedido.Data : (DateTime?)null
        })
    .ToListAsync();

O método RightJoin() segue a mesma assinatura, apenas invertendo a direção da junção.

SELECT [c].[Nome], [p].[Id] AS [PedidoId], [p].[Data] AS [DataPedido]
FROM [Clientes] AS [c]
LEFT OUTER JOIN [Pedidos] AS [p] ON [c].[Id] = [p].[ClienteId]
  • Sem subqueries desnecessárias
  • Sem CROSS APPLY ou APPLY implícito
  • Tradução 1:1 com SQL Server, PostgreSQL e MySQL
  • Otimização de plano idêntica à de um LEFT JOIN escrito manualmente

Exemplo completo: clientes sem pedidos

var clientesSemPedidos = await contexto.Clientes
    .LeftJoin(
        contexto.Pedidos,
        c => c.Id,
        p => p.ClienteId,
        (c, p) => new { Cliente = c, Pedido = p })
    .Where(x => x.Pedido == null)
    .Select(x => x.Cliente)
    .ToListAsync();

Equivalente a:

SELECT [c].*
FROM [Clientes] AS [c]
LEFT OUTER JOIN [Pedidos] AS [p] ON [c].[Id] = [p].[ClienteId]
WHERE [p].[Id] IS NULL

O EF Core 10 mantém o comportamento esperado de eager loading mesmo em junções externas.

Conclusão

A introdução de LeftJoin() e RightJoin() no EF Core 10 não é apenas uma melhoria de conveniência: é uma correção de uma dívida técnica histórica que afetava diretamente a produtividade, a qualidade do código e a confiabilidade das consultas em aplicações .NET corporativas.

A partir de hoje, não há mais justificativa técnica para manter construções baseadas em GroupJoin + DefaultIfEmpty + SelectMany em novos projetos e há forte recomendação para refatorar gradualmente os existentes.

FAQ: Perguntas Frequentes

1. LeftJoin() substitui completamente GroupJoin + DefaultIfEmpty + SelectMany()?

Sim. O padrão antigo torna-se obsoleto a partir do .NET 10, embora mantido por compatibilidade.

2. É possível fazer Full Outer Join?

Sim, combinando LeftJoin com RightJoin ou usando o método FullJoin() (em preview no EF Core 10.1)

3. Funciona com AsSplitQuery() ou AsSingleQuery()?

Sim. O comportamento é idêntico ao de inner joins tradicionais.

4. O SQL gerado é otimizado em todos os provedores?

Sim. Tradução direta para LEFT/RIGHT OUTER JOIN em todos os bancos relacionais suportados.

5. Posso usar com projeção anônima ou DTOs?

  1. Sim. O resultado do join pode ser projetado diretamente em qualquer tipo, incluindo records e DTOs.

Compartilhe:

Tiago Tartari

Tiago Tartari

Eu ajudo e capacito pessoas e organizações a transformar problemas complexos em soluções práticas usando a tecnologia para atingir resultados extraordinários.

Qual é o desafio
que você tem hoje?