الانضمام الخارجي في LINQ

LINQ - كم تم اختراعه في C # فقط حتى نتمكن من الاستمتاع بلذات الاستعلام المتكامل للغة. يسمى:

  • الوراثة
  • طرق التمديد
  • تعابير لامدا
  • أشجار التعبير
  • أنواع Anonumus
  • مُهيئ الكائن
  • اكتب الاستدلال

وهذا كل شيء حتى نتمكن من كتابة شيء مثل هذا:
	var query = 
		from itemA in listA
		join itemB in listB
			on itemA.Key equals itemB.Key
		select new {itemA, itemB};
	

لا يمكن للمرء أن يختلف - يعجب.

وفي خضم كل هذا السكر النحوي كان هناك ملعقة من الندى التي منعتني من الحصول على قسط كافٍ من النوم :)

هذا هو الافتقار التام للدعم من أجل الانضمام الخارجي. ولكن كما اتضح يتحول القطران بسهولة ... يتحول ... يتحول ...

... إلى سكر نحوي آخر.

أولئك الذين حاولوا إيجاد حل لـ LEFT OUTER JOIN على الإنترنت ، ربما يعرفون حلًا مشابهًا:

	var query = 
		from itemA in listA
		join itemB in listB
			on itemA.Key equals itemB.Key into outer
		from itemO in outer.DefaultIfEmpty()
		select new {itemA, itemO};
	

يخلط هذا التصميم بوضوح الفهم بأمر من الحجم ويعقد البناء البسيط بالفعل. وهذا مجرد بديل لـ INNER JOIN بـ LEFT OUTER JOIN. لكي لا تستمر في الصدمة ، لن أعطيك مثالًا على التسجيل الكامل للخارج.

يبدو كما لو كان من السهل أن نكتب هكذا:

	var query = 
		from itemA in listA
		left join itemB in listB
			on itemA.Key equals itemB.Key
		select new {itemA, itemB};
	

أو هكذا

	var query = 
		from itemA in listA
		full join itemB in listB
			on itemA.Key equals itemB.Key
		select new {itemA, itemB};
	

لكن لا. لم يقدم لنا مؤلفو C # مثل هذه المتعة. حسنًا ، لا يهم. ومع ذلك ، سيسمحون لنا بالقيام بذلك بمفردنا ، على الرغم من أنه ليس بهذه الطريقة الجميلة.

بادئ ذي بدء ، إذا أخبرك أحدهم أن LINQ والواجهة System.Collections.Generic.IE التي لا تعد ولا تحصى لها شيء مشترك ولا يمكن أن توجد بشكل منفصل ، يمكنك الضحك بأمان في شخص ...

التصميم

	var query = 
		from itemA in listA
		join itemB in listB
			on itemA.Key equals itemB.Key
		select new {itemA, itemB};

يتم ترجمته ببساطة من قبل المترجم إلى التسلسل التالي من الأحرف:
	var query = listA.Join(listB, itemA => itemA.Key, itemB => itemB.Key, (itemA, itemB) => new {itemA, itemB});

ولا يهم نوع المتغيرات listA و listB. افترض أن listA متغير من النوع TypeA ، وأن العنصر B الثابت من النوع TypeB. لذا ، إذا احتوى TypeA و TypeB على خاصية أو حقل باسم Key ، فإن TypeA يحتوي على أسلوب Join () بأربع وسيطات. يتم تجميع استعلام LINQ هذا بحرية.

عند استخدام المتغيرات في LINQ التي تطبق واجهة IEnumerable القياسية ، يتم استخدام طريقة التمديد

public class System.Linq.Enumerable
{
		public static IEnumerable<TResult> Join<TOuter, TInner, TKey, TResult>(this IEnumerable<TOuter> outer, IEnumerable<TInner> inner, Func<TOuter, TKey> outerKeySelector, Func<TInner, TKey> innerKeySelector, Func<TOuter, TInner, TResult> resultSelector) {...}
}

في الواقع هذه الطريقة تنتج أيضا INNER JOIN المعروفة. والآن يبدأ سحر الشارع. من أجل تنفيذ LEFT / RIGHT / FULL OUTER JOIN (أو JOIN الذي سوف يرضي روحك) ، تحتاج إلى استبدال استدعاء الطريقة القياسية بالطريقة التي نفذناها. للقيام بذلك ، نحتاج إلى تحويل قائمة المتغيرات A بطريقة ما إلى نوع يمكننا التحكم فيه.

من خلال تطبيق الفئتين التاليتين:

public class JoinedEnumerable<T> : IEnumerable<T>
{
	public readonly IEnumerable<T> Source;
	public bool IsOuter;

	public JoinedEnumerable(IEnumerable<T> source) { Source = source; }

	IEnumerator<T> IEnumerable<T>.GetEnumerator() { return Source.GetEnumerator(); }
	IEnumerator IEnumerable.GetEnumerator() { return Source.GetEnumerator(); }
}

public static class JoinedEnumerable
{
	public static JoinedEnumerable<TElement> Inner<TElement>(this IEnumerable<TElement> source)
	{
		return Wrap(source, false);
	}

	public static JoinedEnumerable<TElement> Outer<TElement>(this IEnumerable<TElement> source)
	{
		return Wrap(source, true);
	}

	public static JoinedEnumerable<TElement> Wrap(IEnumerable<TElement> source, bool isOuter)
	{
		JoinedEnumerable<TElement> joinedSource 
			= source as JoinedEnumerable<TElement> ?? 
				new JoinedEnumerable<TElement>(source);
		joinedSource.IsOuter = isOuter;
		return joinedSource;
	}
}

يمكننا بسهولة كتابة استعلام LINQ التالي

	var query = 
		from itemA in listA.Outer()
		join itemB in listB
			on itemA.Key equals itemB.Key
		select new {itemA, itemB};

وننفذ الآن طريقة ملحق الانضمام لفئة JoinEnumerable بالطريقة التي نحتاجها ، نحصل على كل ما نحتاجه.

وإليك طرق التمديد:

public static IEnumerable<TResult> Join<TOuter, TInner, TKey, TResult>(this JoinedEnumerable<TOuter> outer, IEnumerable<TInner> inner, Func<TOuter, TKey> outerKeySelector, Func<TInner, TKey> innerKeySelector, Func<TOuter, TInner, TResult> resultSelector, IEqualityComparer<TKey> comparer = null)
{
	if (outer == null) throw new ArgumentNullException("outer");
	if (inner == null) throw new ArgumentNullException("inner");
	if (outerKeySelector == null) throw new ArgumentNullException("outerKeySelector");
	if (innerKeySelector == null) throw new ArgumentNullException("innerKeySelector");
	if (resultSelector == null) throw new ArgumentNullException("resultSelector");

	bool leftOuter = outer.IsOuter;
	bool rightOuter = (inner is JoinedEnumerable<TInner>) && ((JoinedEnumerable<TInner>)inner).IsOuter;

	if (leftOuter && rightOuter)
		return FullOuterJoin(outer, inner, outerKeySelector, innerKeySelector, resultSelector, comparer);

	if (leftOuter)
		return LeftOuterJoin(outer, inner, outerKeySelector, innerKeySelector, resultSelector, comparer);

	if (rightOuter)
		return RightOuterJoin(outer, inner, outerKeySelector, innerKeySelector, resultSelector, comparer);

	return Enumerable.Join(outer, inner, outerKeySelector, innerKeySelector, resultSelector, comparer);
}

public static IEnumerable<TResult> LeftOuterJoin<TOuter, TInner, TKey, TResult>(this IEnumerable<TOuter> outer, IEnumerable<TInner> inner, Func<TOuter, TKey> outerKeySelector, Func<TInner, TKey> innerKeySelector, Func<TOuter, TInner, TResult> resultSelector, IEqualityComparer<TKey> comparer = null)
{
	var innerLookup = inner.ToLookup(innerKeySelector, comparer);

	foreach (var outerItem in outer)
		foreach (var innerItem in innerLookup[outerKeySelector(outerItem)].DefaultIfEmpty())
			yield return resultSelector(outerItem, innerItem);
}

public static IEnumerable<TResult> RightOuterJoin<TOuter, TInner, TKey, TResult>(this IEnumerable<TOuter> outer, IEnumerable<TInner> inner, Func<TOuter, TKey> outerKeySelector, Func<TInner, TKey> innerKeySelector, Func<TOuter, TInner, TResult> resultSelector, IEqualityComparer<TKey> comparer = null)
{
	var outerLookup = outer.ToLookup(outerKeySelector, comparer);

	foreach (var innerItem in inner)
		foreach (var outerItem in outerLookup[innerKeySelector(innerItem)].DefaultIfEmpty())
			yield return resultSelector(outerItem, innerItem);
}

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, IEqualityComparer<TKey> comparer = null)
{
	var outerLookup = outer.ToLookup(outerKeySelector, comparer);
	var innerLookup = inner.ToLookup(innerKeySelector, comparer);

	foreach (var innerGrouping in innerLookup)
		if (!outerLookup.Contains(innerGrouping.Key))
			foreach (TInner innerItem in innerGrouping)
				yield return resultSelector(default(TOuter), innerItem);

	foreach (var outerGrouping in outerLookup)
		foreach (var innerItem in innerLookup[outerGrouping.Key].DefaultIfEmpty())
			foreach (var outerItem in outerGrouping)
				yield return resultSelector(outerItem, innerItem);
}

Voila ...

جميل آخر اليسار الانضمام:

	var query = 
		from itemA in listA.Outer()
		join itemB in listB
			on itemA.Key equals itemB.Key
		select new {itemA, itemB};

جميل الحق في الانضمام الخارجي:

	var query = 
		from itemA in listA.Inner()
		join itemB in listB.Outer()
			on itemA.Key equals itemB.Key
		select new {itemA, itemB};

جميل كامل خارجي انضم:

	var query = 
		from itemA in listA.Outer()
		join itemB in listB.Outer()
			on itemA.Key equals itemB.Key
		select new {itemA, itemB};

الآن ، إذا كنت ترغب ، يمكنك استخدام نهجك الخاص - حيث أن مجال الخيال ضخم هنا. لدي العديد من الحلول الأكثر إثارة للاهتمام لتنفيذ الأشياء الجيدة في zashashnik. سيكون هناك وقت لمشاركتها.

شكرا للانتباه.

قد تكون السلطة معك!

All Articles