ฉันคิดว่ามีปัญหากับสิ่งเหล่านี้ส่วนใหญ่รวมถึงคำตอบที่ได้รับการยอมรับเพราะพวกเขาทำงานได้ไม่ดีกับ Linq มากกว่า IQueryable ทั้งเนื่องจากการทำเซิร์ฟเวอร์ไปกลับมากเกินไปและส่งคืนข้อมูลมากเกินไปหรือทำการประมวลผลไคลเอ็นต์มากเกินไป
สำหรับ IEnumerable ฉันไม่ชอบคำตอบของ Sehe หรือสิ่งที่คล้ายกันเพราะมีการใช้หน่วยความจำมากเกินไป (การทดสอบสองรายการ 10,000,000 รายการอย่างง่ายวิ่ง Linqpad ออกจากหน่วยความจำในเครื่อง 32GB ของฉัน)
นอกจากนี้คนอื่น ๆ ส่วนใหญ่ไม่ได้ใช้การเข้าร่วมเต็มรูปแบบภายนอกอย่างเหมาะสมเพราะพวกเขากำลังใช้สหภาพที่มีการเข้าร่วมที่ถูกต้องแทนที่จะเข้าร่วม Concat กับการเข้าร่วมกึ่งต่อต้านขวาซึ่งไม่เพียง แต่กำจัดแถวเข้าร่วมด้านในซ้ำจากผลลัพธ์ รายการซ้ำที่เหมาะสมที่มีอยู่เดิมในข้อมูลด้านซ้ายหรือด้านขวา
ดังนั้นนี่คือส่วนขยายของฉันที่จัดการกับปัญหาเหล่านี้ทั้งหมดสร้าง SQL รวมถึงการนำการเข้าร่วมใน LINQ ไปยัง SQL โดยตรงดำเนินการบนเซิร์ฟเวอร์และเร็วกว่าและมีหน่วยความจำน้อยกว่าที่อื่น ๆ บน Enumerables:
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));
}
}
ความแตกต่างระหว่าง Right Anti-Semi-Join นั้นส่วนใหญ่เป็น moot กับ Linq ไปยัง Objects หรือในซอร์ส แต่สร้างความแตกต่างบนฝั่งเซิร์ฟเวอร์ (SQL) ในคำตอบสุดท้ายโดยไม่จำเป็น JOIN
ในคำตอบสุดท้ายลบที่ไม่จำเป็น
การเขียนโค้ดด้วยมือExpression
เพื่อจัดการการรวมExpression<Func<>>
เข้ากับแลมบ์ดานั้นสามารถปรับปรุงได้ด้วย LinqKit แต่มันจะดีถ้าภาษา / คอมไพเลอร์ได้เพิ่มความช่วยเหลือลงไป FullOuterJoinDistinct
และRightOuterJoin
ฟังก์ชั่นจะมีอยู่ครบถ้วน แต่ผมไม่ได้ใช้อีกครั้งFullOuterGroupJoin
เลย
ฉันเขียนการรวมภายนอกแบบเต็มสำหรับอีกรุ่นหนึ่งIEnumerable
สำหรับกรณีที่คีย์สามารถสั่งซื้อได้ซึ่งเร็วกว่าการรวมการรวมภายนอกด้านซ้ายเข้ากับการรวมกึ่งต่อต้านขวาอย่างน้อย 50% อย่างน้อยในคอลเล็กชั่นเล็ก ๆ มันจะผ่านการรวบรวมแต่ละครั้งหลังจากเรียงลำดับเพียงครั้งเดียว
ฉันยังเพิ่มคำตอบสำหรับรุ่นที่ทำงานกับ EF ด้วยการแทนที่Invoke
ด้วยส่วนขยายที่กำหนดเอง