问题:
如何基于公共键字段在两个对象列表之间执行全外连接,确保两个列表中的所有记录都包含在结果中,即使它们在另一个列表中没有对应的匹配项?
答案:
为了在 LINQ 中实现全外连接,我们可以定义一个自定义扩展方法,如下所示:
public static IEnumerable<TResult> FullOuterJoin<TA, TB, TKey, TResult>( this IEnumerable<TA> a, IEnumerable<TB> b, Func<TA, TKey> selectKeyA, Func<TB, TKey> selectKeyB, Func<TA, TB, TKey, TResult> projection, TA defaultA = default(TA), TB defaultB = default(TB), IEqualityComparer<TKey> cmp = null) { cmp = cmp ?? EqualityComparer<TKey>.Default; var alookup = a.ToLookup(selectKeyA, cmp); var blookup = b.ToLookup(selectKeyB, cmp); var keys = new HashSet<TKey>(alookup.Select(p => p.Key), cmp); keys.UnionWith(blookup.Select(p => p.Key)); var join = from key in keys from xa in alookup[key].DefaultIfEmpty(defaultA) from xb in blookup[key].DefaultIfEmpty(defaultB) select projection(xa, xb, key); return join; }
在提供的示例中,我们有两个列表:firstNames
和 lastNames
。要执行全外连接,我们可以使用以下代码:
var outerJoin = from first in firstNames join last in lastNames on first.ID equals last.ID into temp from last in temp.DefaultIfEmpty() select new { id = first != null ? first.ID : last.ID, firstname = first != null ? first.Name : string.Empty, surname = last != null ? last.Name : string.Empty };
此代码使用左外连接,其中 firstNames
中的每条记录都与 lastNames
中的第一条匹配记录连接,如果找不到匹配项,则连接到默认值。into temp
语句创建了一个 lastNames
中匹配记录的临时集合,允许我们应用 DefaultIfEmpty()
以确保包含不匹配的记录。 这实际上实现了左外连接,并非完整意义上的全外连接。 要实现全外连接,需要使用上面自定义的FullOuterJoin
扩展方法。
全外连接的预期输出为:
<code>ID FirstName LastName -- --------- -------- 1 John Doe 2 Sue 3 Smith</code>
提供的扩展方法允许您为不匹配的记录指定默认值。在示例中,我们为不匹配的名称指定了 string.Empty
,为不匹配的 ID 指定了负整数。
全外连接的运行时间为 O(n m),其中 n 和 m 是两个输入列表的长度。这是因为 ToLookup()
操作需要 O(n) 时间,后续操作需要 O(n m) 时间。
全外连接的这种实现目前不是 LINQ 标准的一部分,但已建议将其包含在未来的版本中。 标准LINQ不直接支持全外连接,需要自定义扩展方法来实现。
以上是如何在LINQ中执行完整的外部连接?的详细内容。更多信息请关注PHP中文网其他相关文章!