GroupJoin 是您使用 SelectMany 取消分组的 Left-Outer-Join
示例:有一名或多名学生的学校
- 学校 A 有学生 1、2、7
- B 校有 3、5 名学生,
- C 校没有学生
- D 校有 4、6 名学生,
GroupJoin 只会给出上述内容:拥有零个或多个学生的学校
显然你想要以下结果。
School.Id | Student.Id Student.SchoolId
A | 1 A
B | 3 B
A | 2 A
C | (null)
B | 5 B
D | 4 D
D | 6 D
恕我直言,我更喜欢第一个查询(学校及其学生),但嘿,这是你的设计。
public static IEnumerable<TResult> LeftOuterJoin<TSource, TInner, TKey, TResult>(
this IEnumerable<TSource> outerCollection,
IEnumerable<TInner> innerCollection,
Func<TSource, TKey> outerKeySelector,
Func<TInner, TKey> innerkeySelector,
Func<TSource, TInner, TResult> resultSelector)
{
return outerCollection.GroupJoin(innnerCollection,
outerElement => outerKeySelector(outerElement),
innerElement => innerKeySelector(innerElement),
(outerElement, innerElementsOfThisOuterElement) => new
{
OuterElement = outerElement,
InnerElements = innerElementsOfThisOuterElement,
})
// result: a sequence of outerElements, each with their zero or more innerElements
// use SelectMany to ungroup:
.SelectMany(group => group.InnerElements, // = parameter collectionSelector
// parameter resultSelector:
(group, innerElement) => resultSelector(group.OuterElement, innerElement));
}
言辞:
从innerCollection 中的每个元素,计算一个innerKey,使用innerKeySelector。
从outerCollection的每个元素中找到属于这个outerElement的所有innerElements,通过计算outerKey,使用outerKeySelector并获取所有innerKey等于outerKey的innerElements。
结果是一个组:一个包含 OuterElements 的组,每个组都有零个或多个 InnerElements。
取消组合:从每个组中取出 group.OuterElement 及其每个 InnerElement。对于每个组合(outerElement、innerElement),使用 ResultSelector 创建一个 TResult
如果你只使用一次,我不会为此做一个特殊的方法。只需写 GroupJoin,然后是 SelectMany:
var result = ...
.GroupJoin(...)
.SelectMany(...)
如果您经常使用它,并且计划创建一个单独的方法,请考虑使该方法更高效:
public static IEnumerable<TResult> LeftOuterJoin<TSource, TInner, TKey, TResult>(
this IEnumerable<TSource> outerCollection,
IEnumerable<TInner> innerCollection,
Func<TSource, TKey> outerKeySelector,
Func<TInner, TKey> innerkeySelector,
Func<TSource, TInner, TResult> resultSelector)
{
var innerLookup = innerCollection.ToLookup(innerKeySelector);
foreach (var outerElement in outerCollection)
{
var outerKey = outerKeySelector(outerElement);
var innerElementsOfThisOuterElement = innerLookup[outerKey];
// might be empty!
// for every inner, return combinatino of (outer, inner)
// if inner empty: return (outer, null)
var innerEnumerator = innerElementsOfThisOuterElement.GetEnumerator();
if (innerEnumerator.MoveNext())
{
// there is at least one inner
yield return resultSelector(outerElement, innerEnumerator.Current);
// do the rest:
while enumerator.MoveNext())
yield return resultSelector(outerElement, innerEnumerator.Current);
}
else
{
// no inner
yield return resultSelector(outerElement, null);
}
}
}