我有一个人的 ID 和他们的名字的列表,以及一个人的 ID 和他们的姓氏的列表。有些人没有名字,有些人没有姓氏;我想对两个列表进行完全外部联接。
所以下面的列表:
ID FirstName
-- ---------
1 John
2 Sue
ID LastName
-- --------
1 Doe
3 Smith
应该产生:
ID FirstName LastName
-- --------- --------
1 John Doe
2 Sue
3 Smith
我是 LINQ 的新手(如果我说得不对请原谅我)并且找到了很多“LINQ 外连接”的解决方案,它们看起来都非常相似,但实际上似乎是左外连接。
到目前为止,我的尝试是这样的:
private void OuterJoinTest()
{
List<FirstName> firstNames = new List<FirstName>();
firstNames.Add(new FirstName { ID = 1, Name = "John" });
firstNames.Add(new FirstName { ID = 2, Name = "Sue" });
List<LastName> lastNames = new List<LastName>();
lastNames.Add(new LastName { ID = 1, Name = "Doe" });
lastNames.Add(new LastName { ID = 3, Name = "Smith" });
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
};
}
}
public class FirstName
{
public int ID;
public string Name;
}
public class LastName
{
public int ID;
public string Name;
}
但这会返回:
ID FirstName LastName
-- --------- --------
1 John Doe
2 Sue
我做错了什么?
最佳答案
更新 1:提供真正通用的扩展方法 FullOuterJoin
强>
更新 2:可选择接受自定义 IEqualityComparer
对于 key 类型
更新 3:此实现有 recently become part of MoreLinq
- 谢谢大家!
编辑 添加FullOuterGroupJoin
(ideone)。我重复使用了 GetOuter<>
实现,使它的性能比它可能的要低一些,但我现在的目标是“高级”代码,而不是前沿优化。
在 http://ideone.com/O36nWc 上观看直播
static void Main(string[] args)
{
var ax = new[] {
new { id = 1, name = "John" },
new { id = 2, name = "Sue" } };
var bx = new[] {
new { id = 1, surname = "Doe" },
new { id = 3, surname = "Smith" } };
ax.FullOuterJoin(bx, a => a.id, b => b.id, (a, b, id) => new {a, b})
.ToList().ForEach(Console.WriteLine);
}
打印输出:
{ a = { id = 1, name = John }, b = { id = 1, surname = Doe } }
{ a = { id = 2, name = Sue }, b = }
{ a = , b = { id = 3, surname = Smith } }
您还可以提供默认值: http://ideone.com/kG4kqO
ax.FullOuterJoin(
bx, a => a.id, b => b.id,
(a, b, id) => new { a.name, b.surname },
new { id = -1, name = "(no firstname)" },
new { id = -2, surname = "(no surname)" }
)
打印:
{ name = John, surname = Doe }
{ name = Sue, surname = (no surname) }
{ name = (no firstname), surname = Smith }
所用术语的解释:
Joining 是从关系数据库设计中借用的术语:
- join 将重复
a
中的元素b
中元素的次数使用相应的键(即:如果b
为空,则没有)。 数据库术语称之为inner (equi)join
。 - 外部联接 包含来自
a
的元素没有相应的 元素存在于b
中. (即:即使b
为空,结果也是如此)。 这通常称为left join
。 - 完全外部联接 包括来自
a
的记录以及b
如果没有相应的元素存在于另一个。 (即,即使a
为空,结果也是如此)
在 RDBMS 中通常看到的是组连接[1]:
- 一个group join,和上面描述的一样,但是而不是重复
a
中的元素对于多个对应b
,它分组具有相应键的记录。当您希望基于公用键枚举“连接的”记录时,这通常会更方便。
另见 GroupJoin其中还包含一些一般背景说明。
[1](我相信 Oracle 和 MSSQL 对此有专有扩展)
完整代码
一个通用的“插入式”扩展类
internal static class MyExtensions
{
internal static IEnumerable<TResult> FullOuterGroupJoin<TA, TB, TKey, TResult>(
this IEnumerable<TA> a,
IEnumerable<TB> b,
Func<TA, TKey> selectKeyA,
Func<TB, TKey> selectKeyB,
Func<IEnumerable<TA>, IEnumerable<TB>, TKey, TResult> projection,
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
let xa = alookup[key]
let xb = blookup[key]
select projection(xa, xb, key);
return join;
}
internal 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;
}
}
关于c# - LINQ - 完全外部连接,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/5489987/