C# LINQ-完全外连接
我有一份身份证和名字的清单,还有一份身份证和姓氏的清单。有些人没有名字,有些人没有姓氏;我想在这两个列表上做一个完整的外部连接 因此,以下列表:C# LINQ-完全外连接,c#,.net,linq,outer-join,full-outer-join,C#,.net,Linq,Outer Join,Full Outer Join,我有一份身份证和名字的清单,还有一份身份证和姓氏的清单。有些人没有名字,有些人没有姓氏;我想在这两个列表上做一个完整的外部连接 因此,以下列表: ID FirstName -- --------- 1 John 2 Sue ID LastName -- -------- 1 Doe 3 Smith 应产生: ID FirstName LastName -- --------- -------- 1 John Doe 2 Sue 3
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;
}
var test =
firstNames
.FullJoinDistinct(
lastNames,
f=> f.ID,
j=> j.ID,
(f,j)=> new {
ID = f == null ? j.ID : f.ID,
leftName = f == null ? null : f.Name,
rightName = j == null ? null : j.Name
}
);
我做错了什么?正如您所发现的,Linq没有“外部联接”构造。使用您所述的查询,可以得到的最接近的连接是左外连接。为此,您可以添加lastname列表中未在联接中表示的任何元素:
outerJoin = outerJoin.Concat(lastNames.Select(l=>new
{
id = l.ID,
firstname = String.Empty,
surname = l.Name
}).Where(l=>!outerJoin.Any(o=>o.id == l.id)));
正如您所发现的,Linq没有“外部连接”构造。使用您所述的查询,可以得到的最接近的连接是左外连接。为此,您可以添加lastname列表中未在联接中表示的任何元素:
outerJoin = outerJoin.Concat(lastNames.Select(l=>new
{
id = l.ID,
firstname = String.Empty,
surname = l.Name
}).Where(l=>!outerJoin.Any(o=>o.id == l.id)));
我不知道这是否涵盖了所有情况,从逻辑上看,这似乎是正确的。其思想是采用左外连接和右外连接,然后采用结果的并集
var firstNames = new[]
{
new { ID = 1, Name = "John" },
new { ID = 2, Name = "Sue" },
};
var lastNames = new[]
{
new { ID = 1, Name = "Doe" },
new { ID = 3, Name = "Smith" },
};
var leftOuterJoin =
from first in firstNames
join last in lastNames on first.ID equals last.ID into temp
from last in temp.DefaultIfEmpty()
select new
{
first.ID,
FirstName = first.Name,
LastName = last?.Name,
};
var rightOuterJoin =
from last in lastNames
join first in firstNames on last.ID equals first.ID into temp
from first in temp.DefaultIfEmpty()
select new
{
last.ID,
FirstName = first?.Name,
LastName = last.Name,
};
var fullOuterJoin = leftOuterJoin.Union(rightOuterJoin);
因为它是在LINQ to对象中,所以它可以像写的那样工作。如果LINQ转换为SQL或其他,查询处理器可能不支持安全导航或其他操作。您必须使用条件运算符来有条件地获取值
i、 e
我不知道这是否涵盖了所有情况,从逻辑上看,这似乎是正确的。其思想是采用左外连接和右外连接,然后采用结果的并集
var firstNames = new[]
{
new { ID = 1, Name = "John" },
new { ID = 2, Name = "Sue" },
};
var lastNames = new[]
{
new { ID = 1, Name = "Doe" },
new { ID = 3, Name = "Smith" },
};
var leftOuterJoin =
from first in firstNames
join last in lastNames on first.ID equals last.ID into temp
from last in temp.DefaultIfEmpty()
select new
{
first.ID,
FirstName = first.Name,
LastName = last?.Name,
};
var rightOuterJoin =
from last in lastNames
join first in firstNames on last.ID equals first.ID into temp
from first in temp.DefaultIfEmpty()
select new
{
last.ID,
FirstName = first?.Name,
LastName = last.Name,
};
var fullOuterJoin = leftOuterJoin.Union(rightOuterJoin);
因为它是在LINQ to对象中,所以它可以像写的那样工作。如果LINQ转换为SQL或其他,查询处理器可能不支持安全导航或其他操作。您必须使用条件运算符来有条件地获取值
i、 e
更新1:提供真正通用的扩展方法
FullOuterJoin
更新2:可选择接受密钥类型的自定义
IEqualityComparer
更新3:此实施已完成-谢谢各位 编辑添加了
FullOuterGroupJoin
()。我重用了GetOuter
实现,使它的性能比可能的低了一小部分,但我现在的目标是“高级”代码,而不是前沿优化
在
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);
}
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)" }
)
打印输出:
{ a = { id = 1, name = John }, b = { id = 1, surname = Doe } }
{ a = { id = 2, name = Sue }, b = }
{ a = , b = { id = 3, surname = Smith } }
您还可以提供默认值:
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);
}
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 }
对所用术语的解释:
连接是从关系数据库设计中借用的术语:
- Ajoin将使用相应的键重复
中的元素,重复次数与A
中的元素相同(即:如果b
为空,则不重复)。数据库行话调用这个b
内部(equi)连接
- 外部联接包括来自
的元素,这些元素没有对应的 元素存在于a
中。(即:b
为空时的偶数结果)。这通常被称为b
左连接
- 完全外部联接包括
中的记录,以及A
中不存在对应元素的记录。(即,b
为空时的偶数结果)a
- A组联接,执行与上述相同的操作,但是对于多个相应的
,它不是重复b
中的元素,而是使用相应的键对记录进行分组。当您希望基于公共密钥枚举“已联接”记录时,这通常更方便A
[1] (我相信Oracle和MSSQL对此有专有的扩展) 完整代码 此应用程序的通用“drop-in”扩展类
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;
}
}
内部静态类MyExtensions
{
内部静态IEnumerable FullOuterGroupJoin(
这是数不清的,
IEnumerable b,
Func selectKeyA,
Func selectKeyB,
Func投影,
IEqualityComparer cmp=null)
{
cmp=cmp??相等比较。默认值;
var alookup=a.ToLookup(选择keya,cmp);
var blookup=b.ToLookup(选择键b,cmp);
var Key=newhashset(alookup.Select(p=>p.Key),cmp);
UnionWith(blookup.Select(p=>p.Key));
var join=from键入键
设xa=alookup[key]
设xb=blookup[key]
选择投影(xa、xb、键);
返回加入;
}
内部静态IEnumerable FullOuterJoin(
这是数不清的,
IEnumerable b,
Func selectKeyA,
Func selectKeyB,
Func投影,
TA defaultA=默认值(TA),
TB defaultB=默认值(TB),
IEqualityComparer cmp=null)
{
cmp=cmp??相等比较。默认值;
var alookup=a.ToLookup(选择keya,cmp);
var blookup=b.ToLookup(选择键b,cmp);
var Key=newhashset(alookup.Select(p=>p.Key),cmp);
UnionWith(blookup.Select(p=>p.Key));
var join=from键入键
来自alookup[key]中的xa。DefaultIfEmpty(defaultA)
从blookup[key]中的xb开始。DefaultIfEmpty(defaultB)
选择投影(xa、xb、键);
返回加入;
}
}
更新1:提供真正通用的扩展方法
更新2:可选择接受密钥类型的自定义IEqualityComparer
更新3:此实施已完成-谢谢各位
编辑添加了FullOuterGroupJoin
()。我重用了GetOuter
实现,使其性能比它低了一小部分
select isnull(fn.id, ln.id) as id, fn.firstname, ln.lastname
from firstnames fn
full join lastnames ln on ln.id=fn.id
public static class LinqExtensions
{
public static IEnumerable<TResult> FullOuterJoin<TLeft, TRight, TKey, TResult>(
this IEnumerable<TLeft> left,
IEnumerable<TRight> right,
Func<TLeft, TKey> leftKeySelector,
Func<TRight, TKey> rightKeySelector,
Func<TLeft, TRight, TKey, TResult> resultSelector,
IEqualityComparer<TKey> comparator = null,
TLeft defaultLeft = default(TLeft),
TRight defaultRight = default(TRight))
{
if (left == null) throw new ArgumentNullException("left");
if (right == null) throw new ArgumentNullException("right");
if (leftKeySelector == null) throw new ArgumentNullException("leftKeySelector");
if (rightKeySelector == null) throw new ArgumentNullException("rightKeySelector");
if (resultSelector == null) throw new ArgumentNullException("resultSelector");
comparator = comparator ?? EqualityComparer<TKey>.Default;
return FullOuterJoinIterator(left, right, leftKeySelector, rightKeySelector, resultSelector, comparator, defaultLeft, defaultRight);
}
internal static IEnumerable<TResult> FullOuterJoinIterator<TLeft, TRight, TKey, TResult>(
this IEnumerable<TLeft> left,
IEnumerable<TRight> right,
Func<TLeft, TKey> leftKeySelector,
Func<TRight, TKey> rightKeySelector,
Func<TLeft, TRight, TKey, TResult> resultSelector,
IEqualityComparer<TKey> comparator,
TLeft defaultLeft,
TRight defaultRight)
{
var leftLookup = left.ToLookup(leftKeySelector, comparator);
var rightLookup = right.ToLookup(rightKeySelector, comparator);
var keys = leftLookup.Select(g => g.Key).Union(rightLookup.Select(g => g.Key), comparator);
foreach (var key in keys)
foreach (var leftValue in leftLookup[key].DefaultIfEmpty(defaultLeft))
foreach (var rightValue in rightLookup[key].DefaultIfEmpty(defaultRight))
yield return resultSelector(leftValue, rightValue, key);
}
}
namespace joinext
{
public static class JoinExtensions
{
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)
where TInner : class
where TOuter : class
{
var innerLookup = inner.ToLookup(innerKeySelector);
var outerLookup = outer.ToLookup(outerKeySelector);
var innerJoinItems = inner
.Where(innerItem => !outerLookup.Contains(innerKeySelector(innerItem)))
.Select(innerItem => resultSelector(null, innerItem));
return outer
.SelectMany(outerItem =>
{
var innerItems = innerLookup[outerKeySelector(outerItem)];
return innerItems.Any() ? innerItems : new TInner[] { null };
}, resultSelector)
.Concat(innerJoinItems);
}
public static IEnumerable<TResult> LeftJoin<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,
(o, i) =>
new { o = o, i = i.DefaultIfEmpty() })
.SelectMany(m => m.i.Select(inn =>
resultSelector(m.o, inn)
));
}
public static IEnumerable<TResult> RightJoin<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.GroupJoin(
outer,
innerKeySelector,
outerKeySelector,
(i, o) =>
new { i = i, o = o.DefaultIfEmpty() })
.SelectMany(m => m.o.Select(outt =>
resultSelector(outt, m.i)
));
}
}
}
var result = left.FullOuterJoin(
right,
x=>left.Key,
x=>right.Key,
(l,r) => new { LeftKey = l?.Key, RightKey=r?.Key });
/// <summary>
/// Performs a full outer join on two <see cref="IEnumerable{T}" />.
/// </summary>
/// <typeparam name="TLeft"></typeparam>
/// <typeparam name="TValue"></typeparam>
/// <typeparam name="TRight"></typeparam>
/// <typeparam name="TResult"></typeparam>
/// <param name="left"></param>
/// <param name="right"></param>
/// <param name="leftKeySelector"></param>
/// <param name="rightKeySelector"></param>
/// <param name="selector">Expression defining result type</param>
/// <param name="keyComparer">A comparer if there is no default for the type</param>
/// <returns></returns>
[System.Diagnostics.DebuggerStepThrough]
public static IEnumerable<TResult> FullOuterJoin<TLeft, TRight, TValue, TResult>(
this IEnumerable<TLeft> left,
IEnumerable<TRight> right,
Func<TLeft, TValue> leftKeySelector,
Func<TRight, TValue> rightKeySelector,
Func<TLeft, TRight, TResult> selector,
IComparer<TValue> keyComparer = null)
where TLeft: class
where TRight: class
where TValue : IComparable
{
keyComparer = keyComparer ?? Comparer<TValue>.Default;
using (var enumLeft = left.OrderBy(leftKeySelector).GetEnumerator())
using (var enumRight = right.OrderBy(rightKeySelector).GetEnumerator())
{
var hasLeft = enumLeft.MoveNext();
var hasRight = enumRight.MoveNext();
while (hasLeft || hasRight)
{
var currentLeft = enumLeft.Current;
var valueLeft = hasLeft ? leftKeySelector(currentLeft) : default(TValue);
var currentRight = enumRight.Current;
var valueRight = hasRight ? rightKeySelector(currentRight) : default(TValue);
int compare =
!hasLeft ? 1
: !hasRight ? -1
: keyComparer.Compare(valueLeft, valueRight);
switch (compare)
{
case 0:
// The selector matches. An inner join is achieved
yield return selector(currentLeft, currentRight);
hasLeft = enumLeft.MoveNext();
hasRight = enumRight.MoveNext();
break;
case -1:
yield return selector(currentLeft, default(TRight));
hasLeft = enumLeft.MoveNext();
break;
case 1:
yield return selector(default(TLeft), currentRight);
hasRight = enumRight.MoveNext();
break;
}
}
}
}
public static class Ext {
public static IEnumerable<TResult> LeftOuterJoin<TLeft, TRight, TKey, TResult>(
this IEnumerable<TLeft> leftItems,
IEnumerable<TRight> rightItems,
Func<TLeft, TKey> leftKeySelector,
Func<TRight, TKey> rightKeySelector,
Func<TLeft, TRight, TResult> resultSelector) {
return from left in leftItems
join right in rightItems on leftKeySelector(left) equals rightKeySelector(right) into temp
from right in temp.DefaultIfEmpty()
select resultSelector(left, right);
}
public static IEnumerable<TResult> RightOuterJoin<TLeft, TRight, TKey, TResult>(
this IEnumerable<TLeft> leftItems,
IEnumerable<TRight> rightItems,
Func<TLeft, TKey> leftKeySelector,
Func<TRight, TKey> rightKeySelector,
Func<TLeft, TRight, TResult> resultSelector) {
return from right in rightItems
join left in leftItems on rightKeySelector(right) equals leftKeySelector(left) into temp
from left in temp.DefaultIfEmpty()
select resultSelector(left, right);
}
public static IEnumerable<TResult> FullOuterJoinDistinct<TLeft, TRight, TKey, TResult>(
this IEnumerable<TLeft> leftItems,
IEnumerable<TRight> rightItems,
Func<TLeft, TKey> leftKeySelector,
Func<TRight, TKey> rightKeySelector,
Func<TLeft, TRight, TResult> resultSelector) {
return leftItems.LeftOuterJoin(rightItems, leftKeySelector, rightKeySelector, resultSelector).Union(leftItems.RightOuterJoin(rightItems, leftKeySelector, rightKeySelector, resultSelector));
}
public static IEnumerable<TResult> RightAntiSemiJoin<TLeft, TRight, TKey, TResult>(
this IEnumerable<TLeft> leftItems,
IEnumerable<TRight> rightItems,
Func<TLeft, TKey> leftKeySelector,
Func<TRight, TKey> rightKeySelector,
Func<TLeft, TRight, TResult> resultSelector) {
var hashLK = new HashSet<TKey>(from l in leftItems select leftKeySelector(l));
return rightItems.Where(r => !hashLK.Contains(rightKeySelector(r))).Select(r => resultSelector(default(TLeft),r));
}
public static IEnumerable<TResult> FullOuterJoin<TLeft, TRight, TKey, TResult>(
this IEnumerable<TLeft> leftItems,
IEnumerable<TRight> rightItems,
Func<TLeft, TKey> leftKeySelector,
Func<TRight, TKey> rightKeySelector,
Func<TLeft, TRight, TResult> resultSelector) where TLeft : class {
return leftItems.LeftOuterJoin(rightItems, leftKeySelector, rightKeySelector, resultSelector).Concat(leftItems.RightAntiSemiJoin(rightItems, leftKeySelector, rightKeySelector, resultSelector));
}
private static Expression<Func<TP, TC, TResult>> CastSMBody<TP, TC, TResult>(LambdaExpression ex, TP unusedP, TC unusedC, TResult unusedRes) => (Expression<Func<TP, TC, TResult>>)ex;
public static IQueryable<TResult> LeftOuterJoin<TLeft, TRight, TKey, TResult>(
this IQueryable<TLeft> leftItems,
IQueryable<TRight> rightItems,
Expression<Func<TLeft, TKey>> leftKeySelector,
Expression<Func<TRight, TKey>> rightKeySelector,
Expression<Func<TLeft, TRight, TResult>> resultSelector) {
var sampleAnonLR = new { left = default(TLeft), rightg = default(IEnumerable<TRight>) };
var parmP = Expression.Parameter(sampleAnonLR.GetType(), "p");
var parmC = Expression.Parameter(typeof(TRight), "c");
var argLeft = Expression.PropertyOrField(parmP, "left");
var newleftrs = CastSMBody(Expression.Lambda(Expression.Invoke(resultSelector, argLeft, parmC), parmP, parmC), sampleAnonLR, default(TRight), default(TResult));
return leftItems.AsQueryable().GroupJoin(rightItems, leftKeySelector, rightKeySelector, (left, rightg) => new { left, rightg }).SelectMany(r => r.rightg.DefaultIfEmpty(), newleftrs);
}
public static IQueryable<TResult> RightOuterJoin<TLeft, TRight, TKey, TResult>(
this IQueryable<TLeft> leftItems,
IQueryable<TRight> rightItems,
Expression<Func<TLeft, TKey>> leftKeySelector,
Expression<Func<TRight, TKey>> rightKeySelector,
Expression<Func<TLeft, TRight, TResult>> resultSelector) {
var sampleAnonLR = new { leftg = default(IEnumerable<TLeft>), right = default(TRight) };
var parmP = Expression.Parameter(sampleAnonLR.GetType(), "p");
var parmC = Expression.Parameter(typeof(TLeft), "c");
var argRight = Expression.PropertyOrField(parmP, "right");
var newrightrs = CastSMBody(Expression.Lambda(Expression.Invoke(resultSelector, parmC, argRight), parmP, parmC), sampleAnonLR, default(TLeft), default(TResult));
return rightItems.GroupJoin(leftItems, rightKeySelector, leftKeySelector, (right, leftg) => new { leftg, right }).SelectMany(l => l.leftg.DefaultIfEmpty(), newrightrs);
}
public static IQueryable<TResult> FullOuterJoinDistinct<TLeft, TRight, TKey, TResult>(
this IQueryable<TLeft> leftItems,
IQueryable<TRight> rightItems,
Expression<Func<TLeft, TKey>> leftKeySelector,
Expression<Func<TRight, TKey>> rightKeySelector,
Expression<Func<TLeft, TRight, TResult>> resultSelector) {
return leftItems.LeftOuterJoin(rightItems, leftKeySelector, rightKeySelector, resultSelector).Union(leftItems.RightOuterJoin(rightItems, leftKeySelector, rightKeySelector, resultSelector));
}
private static Expression<Func<TP, TResult>> CastSBody<TP, TResult>(LambdaExpression ex, TP unusedP, TResult unusedRes) => (Expression<Func<TP, TResult>>)ex;
public static IQueryable<TResult> RightAntiSemiJoin<TLeft, TRight, TKey, TResult>(
this IQueryable<TLeft> leftItems,
IQueryable<TRight> rightItems,
Expression<Func<TLeft, TKey>> leftKeySelector,
Expression<Func<TRight, TKey>> rightKeySelector,
Expression<Func<TLeft, TRight, TResult>> resultSelector) {
var sampleAnonLgR = new { leftg = default(IEnumerable<TLeft>), right = default(TRight) };
var parmLgR = Expression.Parameter(sampleAnonLgR.GetType(), "lgr");
var argLeft = Expression.Constant(default(TLeft), typeof(TLeft));
var argRight = Expression.PropertyOrField(parmLgR, "right");
var newrightrs = CastSBody(Expression.Lambda(Expression.Invoke(resultSelector, argLeft, argRight), parmLgR), sampleAnonLgR, default(TResult));
return rightItems.GroupJoin(leftItems, rightKeySelector, leftKeySelector, (right, leftg) => new { leftg, right }).Where(lgr => !lgr.leftg.Any()).Select(newrightrs);
}
public static IQueryable<TResult> FullOuterJoin<TLeft, TRight, TKey, TResult>(
this IQueryable<TLeft> leftItems,
IQueryable<TRight> rightItems,
Expression<Func<TLeft, TKey>> leftKeySelector,
Expression<Func<TRight, TKey>> rightKeySelector,
Expression<Func<TLeft, TRight, TResult>> resultSelector) {
return leftItems.LeftOuterJoin(rightItems, leftKeySelector, rightKeySelector, resultSelector).Concat(leftItems.RightAntiSemiJoin(rightItems, leftKeySelector, rightKeySelector, resultSelector));
}
}
public static class Ext {
private static Expression<Func<TP, TC, TResult>> CastSMBody<TP, TC, TResult>(LambdaExpression ex, TP unusedP, TC unusedC, TResult unusedRes) => (Expression<Func<TP, TC, TResult>>)ex;
public static IQueryable<TResult> LeftOuterJoin<TLeft, TRight, TKey, TResult>(
this IQueryable<TLeft> leftItems,
IQueryable<TRight> rightItems,
Expression<Func<TLeft, TKey>> leftKeySelector,
Expression<Func<TRight, TKey>> rightKeySelector,
Expression<Func<TLeft, TRight, TResult>> resultSelector) {
// (lrg,r) => resultSelector(lrg.left, r)
var sampleAnonLR = new { left = default(TLeft), rightg = default(IEnumerable<TRight>) };
var parmP = Expression.Parameter(sampleAnonLR.GetType(), "lrg");
var parmC = Expression.Parameter(typeof(TRight), "r");
var argLeft = Expression.PropertyOrField(parmP, "left");
var newleftrs = CastSMBody(Expression.Lambda(resultSelector.Apply(argLeft, parmC), parmP, parmC), sampleAnonLR, default(TRight), default(TResult));
return leftItems.GroupJoin(rightItems, leftKeySelector, rightKeySelector, (left, rightg) => new { left, rightg }).SelectMany(r => r.rightg.DefaultIfEmpty(), newleftrs);
}
public static IQueryable<TResult> RightOuterJoin<TLeft, TRight, TKey, TResult>(
this IQueryable<TLeft> leftItems,
IQueryable<TRight> rightItems,
Expression<Func<TLeft, TKey>> leftKeySelector,
Expression<Func<TRight, TKey>> rightKeySelector,
Expression<Func<TLeft, TRight, TResult>> resultSelector) {
// (lgr,l) => resultSelector(l, lgr.right)
var sampleAnonLR = new { leftg = default(IEnumerable<TLeft>), right = default(TRight) };
var parmP = Expression.Parameter(sampleAnonLR.GetType(), "lgr");
var parmC = Expression.Parameter(typeof(TLeft), "l");
var argRight = Expression.PropertyOrField(parmP, "right");
var newrightrs = CastSMBody(Expression.Lambda(resultSelector.Apply(parmC, argRight), parmP, parmC), sampleAnonLR, default(TLeft), default(TResult));
return rightItems.GroupJoin(leftItems, rightKeySelector, leftKeySelector, (right, leftg) => new { leftg, right })
.SelectMany(l => l.leftg.DefaultIfEmpty(), newrightrs);
}
private static Expression<Func<TParm, TResult>> CastSBody<TParm, TResult>(LambdaExpression ex, TParm unusedP, TResult unusedRes) => (Expression<Func<TParm, TResult>>)ex;
public static IQueryable<TResult> RightAntiSemiJoin<TLeft, TRight, TKey, TResult>(
this IQueryable<TLeft> leftItems,
IQueryable<TRight> rightItems,
Expression<Func<TLeft, TKey>> leftKeySelector,
Expression<Func<TRight, TKey>> rightKeySelector,
Expression<Func<TLeft, TRight, TResult>> resultSelector) where TLeft : class where TRight : class where TResult : class {
// newrightrs = lgr => resultSelector(default(TLeft), lgr.right)
var sampleAnonLgR = new { leftg = (IEnumerable<TLeft>)null, right = default(TRight) };
var parmLgR = Expression.Parameter(sampleAnonLgR.GetType(), "lgr");
var argLeft = Expression.Constant(default(TLeft), typeof(TLeft));
var argRight = Expression.PropertyOrField(parmLgR, "right");
var newrightrs = CastSBody(Expression.Lambda(resultSelector.Apply(argLeft, argRight), parmLgR), sampleAnonLgR, default(TResult));
return rightItems.GroupJoin(leftItems, rightKeySelector, leftKeySelector, (right, leftg) => new { leftg, right }).Where(lgr => !lgr.leftg.Any()).Select(newrightrs);
}
public static IQueryable<TResult> FullOuterJoin<TLeft, TRight, TKey, TResult>(
this IQueryable<TLeft> leftItems,
IQueryable<TRight> rightItems,
Expression<Func<TLeft, TKey>> leftKeySelector,
Expression<Func<TRight, TKey>> rightKeySelector,
Expression<Func<TLeft, TRight, TResult>> resultSelector) where TLeft : class where TRight : class where TResult : class {
return leftItems.LeftOuterJoin(rightItems, leftKeySelector, rightKeySelector, resultSelector).Concat(leftItems.RightAntiSemiJoin(rightItems, leftKeySelector, rightKeySelector, resultSelector));
}
public static Expression Apply(this LambdaExpression e, params Expression[] args) {
var b = e.Body;
foreach (var pa in e.Parameters.Cast<ParameterExpression>().Zip(args, (p, a) => (p, a))) {
b = b.Replace(pa.p, pa.a);
}
return b.PropagateNull();
}
public static Expression Replace(this Expression orig, Expression from, Expression to) => new ReplaceVisitor(from, to).Visit(orig);
public class ReplaceVisitor : System.Linq.Expressions.ExpressionVisitor {
public readonly Expression from;
public readonly Expression to;
public ReplaceVisitor(Expression _from, Expression _to) {
from = _from;
to = _to;
}
public override Expression Visit(Expression node) => node == from ? to : base.Visit(node);
}
public static Expression PropagateNull(this Expression orig) => new NullVisitor().Visit(orig);
public class NullVisitor : System.Linq.Expressions.ExpressionVisitor {
public override Expression Visit(Expression node) {
if (node is MemberExpression nme && nme.Expression is ConstantExpression nce && nce.Value == null)
return Expression.Constant(null, nce.Type.GetMember(nme.Member.Name).Single().GetMemberType());
else
return base.Visit(node);
}
}
public static Type GetMemberType(this MemberInfo member) {
switch (member) {
case FieldInfo mfi:
return mfi.FieldType;
case PropertyInfo mpi:
return mpi.PropertyType;
case EventInfo mei:
return mei.EventHandlerType;
default:
throw new ArgumentException("MemberInfo must be if type FieldInfo, PropertyInfo or EventInfo", nameof(member));
}
}
}
var DatesA = from A in db.T1 select A.Date;
var DatesB = from B in db.T2 select B.Date;
var DatesC = from C in db.T3 select C.Date;
var Dates = DatesA.Union(DatesB).Union(DatesC);
var Full_Outer_Join =
(from A in Dates
join B in db.T1
on A equals B.Date into AB
from ab in AB.DefaultIfEmpty()
join C in db.T2
on A equals C.Date into ABC
from abc in ABC.DefaultIfEmpty()
join D in db.T3
on A equals D.Date into ABCD
from abcd in ABCD.DefaultIfEmpty()
select new { A, ab, abc, abcd })
.AsEnumerable();
private static IEnumerable<TResult> FullOuterJoin<Ta, Tb, TKey, TResult>(
IEnumerable<Ta> a, IEnumerable<Tb> b,
Func<Ta, TKey> key_a, Func<Tb, TKey> key_b,
Func<Ta, Tb, TResult> selector)
{
var alookup = a.ToLookup(key_a);
var blookup = b.ToLookup(key_b);
var keys = new HashSet<TKey>(alookup.Select(p => p.Key));
keys.UnionWith(blookup.Select(p => p.Key));
return keys.Select(key => selector(alookup[key].FirstOrDefault(), blookup[key].FirstOrDefault()));
}
var ax = new[] {
new { id = 1, first_name = "ali" },
new { id = 2, first_name = "mohammad" } };
var bx = new[] {
new { id = 1, last_name = "rezaei" },
new { id = 3, last_name = "kazemi" } };
var list = FullOuterJoin(ax, bx, a => a.id, b => b.id, (a, b) => "f: " + a?.first_name + " l: " + b?.last_name).ToArray();
f: ali l: rezaei
f: mohammad l:
f: l: kazemi
class Program
{
static void Main(string[] args)
{
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" });
HashSet<int> ids = new HashSet<int>();
foreach (var name in firstNames)
{
ids.Add(name.ID);
}
foreach (var name in lastNames)
{
ids.Add(name.ID);
}
List<FullName> fullNames = new List<FullName>();
foreach (int id in ids)
{
FullName fullName = new FullName();
fullName.ID = id;
FirstName firstName = firstNames.Find(f => f.ID == id);
fullName.FirstName = firstName != null ? firstName.Name : string.Empty;
LastName lastName = lastNames.Find(l => l.ID == id);
fullName.LastName = lastName != null ? lastName.Name : string.Empty;
fullNames.Add(fullName);
}
}
}
public class FirstName
{
public int ID;
public string Name;
}
public class LastName
{
public int ID;
public string Name;
}
class FullName
{
public int ID;
public string FirstName;
public string LastName;
}
List<int> firstIds = firstNames.Select(f => f.ID).ToList();
List<int> LastIds = lastNames.Select(l => l.ID).ToList();
HashSet<int> ids = new HashSet<int>(firstIds.Union(LastIds));//Only unique IDs will be included in HashSet
Module MyExtensions
<Extension()>
Friend Function FullOuterJoin(Of TA, TB, TResult)(ByVal a As IEnumerable(Of TA), ByVal b As IEnumerable(Of TB), ByVal joinPredicate As Func(Of TA, TB, Boolean), ByVal projection As Func(Of TA, TB, TResult), ByVal comparer As IEqualityComparer(Of TResult)) As IEnumerable(Of TResult)
Dim joinL =
From xa In a
From xb In b.Where(Function(x) joinPredicate(xa, x)).DefaultIfEmpty()
Select projection(xa, xb)
Dim joinR =
From xb In b
From xa In a.Where(Function(x) joinPredicate(x, xb)).DefaultIfEmpty()
Select projection(xa, xb)
Return joinL.Union(joinR, comparer)
End Function
End Module
Dim fullOuterJoin = lefts.FullOuterJoin(
rights,
Function(left, right) left.Code = right.Code And (left.Amount [...] Or left.Description.Contains [...]),
Function(left, right) New CompareResult(left, right),
New MyEqualityComparer
)
Public Class MyEqualityComparer
Implements IEqualityComparer(Of CompareResult)
Private Function GetMsg(obj As CompareResult) As String
Dim msg As String = ""
msg &= obj.Code & "_"
[...]
Return msg
End Function
Public Overloads Function Equals(x As CompareResult, y As CompareResult) As Boolean Implements IEqualityComparer(Of CompareResult).Equals
Return Me.GetMsg(x) = Me.GetMsg(y)
End Function
Public Overloads Function GetHashCode(obj As CompareResult) As Integer Implements IEqualityComparer(Of CompareResult).GetHashCode
Return Me.GetMsg(obj).GetHashCode
End Function
End Class
using System;
using System.Collections.Generic;
using System.Linq;
namespace NS
{
public static class DataReunion
{
public static List<Tuple<T1, T2>> FullJoin<T1, T2, TKey>(List<T1> List1, Func<T1, TKey> KeyFunc1, List<T2> List2, Func<T2, TKey> KeyFunc2)
{
List<Tuple<T1, T2>> result = new List<Tuple<T1, T2>>();
Tuple<TKey, T1>[] identifiedList1 = List1.Select(_ => Tuple.Create(KeyFunc1(_), _)).OrderBy(_ => _.Item1).ToArray();
Tuple<TKey, T2>[] identifiedList2 = List2.Select(_ => Tuple.Create(KeyFunc2(_), _)).OrderBy(_ => _.Item1).ToArray();
identifiedList1.Where(_ => !identifiedList2.Select(__ => __.Item1).Contains(_.Item1)).ToList().ForEach(_ => {
result.Add(Tuple.Create<T1, T2>(_.Item2, default(T2)));
});
result.AddRange(
identifiedList1.Join(identifiedList2, left => left.Item1, right => right.Item1, (left, right) => Tuple.Create<T1, T2>(left.Item2, right.Item2)).ToList()
);
identifiedList2.Where(_ => !identifiedList1.Select(__ => __.Item1).Contains(_.Item1)).ToList().ForEach(_ => {
result.Add(Tuple.Create<T1, T2>(default(T1), _.Item2));
});
return result;
}
}
}
using System;
using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NS;
namespace Tests
{
[TestClass]
public class DataReunionTest
{
[TestMethod]
public void Test()
{
List<Tuple<Int32, Int32, String>> A = new List<Tuple<Int32, Int32, String>>();
List<Tuple<Int32, Int32, String>> B = new List<Tuple<Int32, Int32, String>>();
Random rnd = new Random();
/* Comment the testing block you do not want to run
/* Solution to test a wide range of keys*/
for (int i = 0; i < 500; i += 1)
{
A.Add(Tuple.Create(rnd.Next(1, 101), rnd.Next(1, 101), "A"));
B.Add(Tuple.Create(rnd.Next(1, 101), rnd.Next(1, 101), "B"));
}
/* Solution for essential testing*/
A.Add(Tuple.Create(1, 2, "B11"));
A.Add(Tuple.Create(1, 2, "B12"));
A.Add(Tuple.Create(1, 3, "C11"));
A.Add(Tuple.Create(1, 3, "C12"));
A.Add(Tuple.Create(1, 3, "C13"));
A.Add(Tuple.Create(1, 4, "D1"));
B.Add(Tuple.Create(1, 1, "A21"));
B.Add(Tuple.Create(1, 1, "A22"));
B.Add(Tuple.Create(1, 1, "A23"));
B.Add(Tuple.Create(1, 2, "B21"));
B.Add(Tuple.Create(1, 2, "B22"));
B.Add(Tuple.Create(1, 2, "B23"));
B.Add(Tuple.Create(1, 3, "C2"));
B.Add(Tuple.Create(1, 5, "E2"));
Func<Tuple<Int32, Int32, String>, Tuple<Int32, Int32>> key = (_) => Tuple.Create(_.Item1, _.Item2);
var watch = System.Diagnostics.Stopwatch.StartNew();
var res = DataReunion.FullJoin(A, key, B, key);
watch.Stop();
var elapsedMs = watch.ElapsedMilliseconds;
String aser = JToken.FromObject(res).ToString(Formatting.Indented);
Console.Write(elapsedMs);
}
}