It is nice to have some more joining possibilities in LINQ. This page tries to explain how they work. Credits to
Solid Code for some investigation.
EditInner Join
The default join in LINQ is inner join. It may also be achieved using the
Join operator (see
Join Operators).
EditLeft Outer Join
A left outer join can be achieved by using the
GroupJoin operator (see
Join Operators).
The left outer join can be described with a join clause as follows (see
How to: Perform Left Outer Joins):
from outerItem in outer
join innerItem in inner on outerKey equals innerKey into innerGroup
from innerGroupItem in innerGroup.DefaultIfEmpty()
select resultSelector(outerItem, innerGroupItem)
This can be written to an extension function with:
public static IEnumerable<TResult> LeftOuterJoin<TOuter, TInner, TKey, TResult>(
this IEnumerable<TOuter> outer, IEnumerable<TInner> inner,
Func<TOuter, TKey> outerKeySelector, Func<TInner, TKey> innerKeySelector,
Func<TOuter, TInner, TResult> resultSelector)
{
return outer.GroupJoin(inner, outerKeySelector, innerKeySelector,
(outerItem, innerGroup) => innerGroup.DefaultIfEmpty()
.Select(innerGroupItem => resultSelector(outerItem, innerGroupItem))
).SelectMany(result => result);
}
EditRight Outer Join
Right outer join is like left outer join, but with tables switched:
public static IEnumerable<TResult> RightOuterJoin<TOuter, TInner, TKey, TResult>(
this IEnumerable<TOuter> outer, IEnumerable<TInner> inner,
Func<TOuter, TKey> outerKeySelector, Func<TInner, TKey> innerKeySelector,
Func<TOuter, TInner, TResult> resultSelector)
{
return inner.LeftOuterJoin(outer,
innerKeySelector, outerKeySelector,
(innerItem, outerItem) => resultSelector(outerItem, innerItem)
);
}
EditFull Outer Join
Next step is to create a full outer join as a union of left outer join and right outer join.
Using the extension functions we just created:
public static IEnumerable<TResult> FullOuterJoin<TOuter, TInner, TKey, TResult>(
this IEnumerable<TOuter> outer, IEnumerable<TInner> inner,
Func<TOuter, TKey> outerKeySelector, Func<TInner, TKey> innerKeySelector,
Func<TOuter, TInner, TResult> resultSelector)
{
return outer.LeftOuterJoin(inner, outerKeySelector, innerKeySelector, resultSelector).Union(
outer.RightOuterJoin(inner, outerKeySelector, innerKeySelector, resultSelector)
);
}
Notice one thing: the union here
depends on the equality comparison between the results.
If the equality between results is not implemented, the result will therefore contain unexpected duplicates. A safer implementation is the following:
public static IEnumerable<TResult> FullOuterJoin<TOuter, TInner, TKey, TResult>(
this IEnumerable<TOuter> outer, IEnumerable<TInner> inner,
Func<TOuter, TKey> outerKeySelector, Func<TInner, TKey> innerKeySelector,
Func<TOuter, TInner, TResult> resultSelector)
{
return outer.LeftOuterJoin(inner, outerKeySelector, innerKeySelector, resultSelector).Concat(
inner.Where(innerItem =>
!outer.Select(outerItem => outerKeySelector(outerItem)).Contains(innerKeySelector(innerItem))
).Select(innerItem => resultSelector(default(TOuter), innerItem))
);
}