Outer Gabung di LINQ

LINQ - berapa banyak yang telah ditemukan di C # hanya agar kita dapat menikmati kelezatan Query Integrated Language. Yaitu:

  • Generik
  • Metode penyuluhan
  • Ekspresi lamda
  • Pohon ekspresi
  • Jenis anonumus
  • Inisialisasi objek
  • Ketik menyimpulkan

Dan itu semua agar kita dapat menulis sesuatu seperti ini:
	var query = 
		from itemA in listA
		join itemB in listB
			on itemA.Key equals itemB.Key
		select new {itemA, itemB};
	

Seseorang tidak dapat tidak setuju - terkesan.

Dan di tengah-tengah semua gula sintaksis ini ada sendok embun yang mencegah saya tidur cukup :)

Ini adalah kurangnya dukungan untuk OUTER JOIN. Tapi ternyata tar mudah berubah ... berputar ... berputar ...

... menjadi gula sintaksis lain.

Mereka yang mencoba mencari solusi untuk LEFT OUTER BERGABUNG di Internet, mungkin tahu solusi yang serupa:

	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};
	

Desain seperti itu jelas membingungkan pemahaman dengan urutan besarnya dan menyulitkan konstruksi yang sudah sederhana. Dan ini hanya pengganti INNER JOIN dengan LEFT OUTER JOIN. Agar tidak terus kaget, saya tidak akan memberikan contoh dengan FULL OUTER JOIN.

Tampaknya akan sederhana jika kita dapat menulis seperti ini:

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

atau lebih

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

Tapi tidak. Para penulis C # tidak memberi kami kesenangan seperti itu. Yah, itu tidak masalah. Namun demikian, mereka akan memungkinkan kita untuk melakukan ini sendiri, meskipun tidak dengan cara yang indah.

Pertama-tama, jika seseorang memberi tahu Anda bahwa LINQ dan System.Collections.Generic.IEnumerable interface memiliki sesuatu yang sama dan tidak dapat ada secara terpisah, Anda dapat dengan aman tertawa secara langsung ...

Desain

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

itu hanya diterjemahkan oleh kompiler ke dalam urutan karakter berikut:
	var query = listA.Join(listB, itemA => itemA.Key, itemB => itemB.Key, (itemA, itemB) => new {itemA, itemB});

dan tidak masalah apa jenis variabel listA dan listB. Misalkan listA adalah variabel tipe TypeA, dan itemB yang diijinkan adalah tipe TypeB. Jadi, jika TypeA dan TypeB berisi properti atau bidang bernama Key, TypeA berisi metode Join () dengan 4 argumen. Permintaan LINQ ini dikompilasi secara bebas.

Saat menggunakan variabel dalam LINQ yang mengimplementasikan antarmuka standar IEnumerable, metode ekstensi digunakan

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) {...}
}

Sebenarnya metode ini juga menghasilkan INNER JOIN yang terkenal. Dan sekarang sihir jalanan dimulai. Untuk menerapkan GABUNGAN KIRI / KANAN / PENUH OUTER (atau GABUNG yang akan menyenangkan hati Anda), perlu untuk mengganti panggilan metode standar dengan yang kami terapkan. Untuk melakukan ini, kita perlu mengonversi listA variabel dengan beberapa cara menjadi tipe yang dapat kita kontrol.

Dengan menerapkan dua kelas berikut:

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;
	}
}

kita dapat dengan mudah menulis kueri LINQ berikutnya

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

dan sekarang menerapkan metode ekstensi Bergabung untuk kelas JoinEnumerable dengan cara yang kita butuhkan, kita mendapatkan semua yang kita butuhkan.

Dan berikut adalah metode penyuluhannya:

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 ...

Beautiful LEFT OUTER JOIN:

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

OUTER YANG BAGUS, BERGABUNG:

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

Beautiful FULL OUTER JOIN:

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

Sekarang, jika Anda mau, Anda dapat menggunakan pendekatan Anda sendiri - karena bidang imajinasi sangat besar di sini. Saya punya beberapa solusi menarik untuk mengimplementasikan barang di zashashnik. Akan ada waktu untuk membagikannya.

Terimakasih atas perhatiannya.

Semoga DAYA bersamamu!

All Articles