You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
LINQ provides Join (inner join), LeftJoin, and RightJoin, but no full outer join. A full outer join returns all elements from both sequences: matched elements are paired, while unmatched elements from either side appear with default for the missing counterpart. This is one of the most common relational join types and its absence forces users to write verbose, error-prone manual implementations combining LeftJoin with additional Except/Concat logic.
This proposal follows the same design established by LeftJoin/RightJoin (#113) and adds tuple-returning overloads per #120596.
// Result selector overloadvarresults=customers.FullJoin(orders,
c =>c.CustomerId,
o =>o.CustomerId,(customer,order)=>new{CustomerName=customer?.Name??"(no customer)",OrderId=order?.Id??0});// Tuple overload (no result selector needed)varpairs=customers.FullJoin(orders,
c =>c.CustomerId,
o =>o.CustomerId);foreach(var(customer,order)inpairs){if(customeris not null&&orderis not null)Console.WriteLine($"Matched: {customer.Name} - Order #{order.Id}");elseif(customeris not null)Console.WriteLine($"Customer with no orders: {customer.Name}");elseConsole.WriteLine($"Orphaned order: #{order!.Id}");}
Design Decisions
Result selector receives TOuter? and TInner? (both nullable) — unlike LeftJoin where only the inner side is nullable, or RightJoin where only the outer side is nullable. In a full join, either side can be default.
Algorithm uses HashSet<Grouping> to track matched inner groupings — Grouping<TKey, TElement> is a sealed class so default reference equality works correctly. This avoids iterating the inner lookup twice.
Deferred execution — consistent with all other join operators.
Alternative Designs
Users can manually compose LeftJoin + RightJoin + filtering, but this is verbose, requires two passes over the inner sequence, and is error-prone. A dedicated FullJoin is more discoverable, efficient, and idiomatic.
Risks
No source-breaking changes — FullJoin is a new method name with no overload conflicts against existing LINQ operators.
See dotnet/efcore#37633 for the EF/database motivation for this.
Background and motivation
LINQ provides
Join(inner join),LeftJoin, andRightJoin, but no full outer join. A full outer join returns all elements from both sequences: matched elements are paired, while unmatched elements from either side appear withdefaultfor the missing counterpart. This is one of the most common relational join types and its absence forces users to write verbose, error-prone manual implementations combiningLeftJoinwith additionalExcept/Concatlogic.This proposal follows the same design established by
LeftJoin/RightJoin(#113) and adds tuple-returning overloads per #120596.API Proposal
API Usage
Design Decisions
TOuter?andTInner?(both nullable) — unlikeLeftJoinwhere only the inner side is nullable, orRightJoinwhere only the outer side is nullable. In a full join, either side can bedefault.(TOuter? Outer, TInner? Inner)— consistent with the tuple-returning overload pattern proposed in Add Join/LeftJoin/RightJoin overloads without a result selector, returning tuples #120596.HashSet<Grouping>to track matched inner groupings —Grouping<TKey, TElement>is a sealed class so default reference equality works correctly. This avoids iterating the inner lookup twice.Alternative Designs
Users can manually compose
LeftJoin+RightJoin+ filtering, but this is verbose, requires two passes over the inner sequence, and is error-prone. A dedicatedFullJoinis more discoverable, efficient, and idiomatic.Risks
FullJoinis a new method name with no overload conflicts against existing LINQ operators.Related Issues
FullJoinLeftJoin/RightJoinimplementationPrototype
eiriktsarpalis@26b48d9