Saya memiliki daftar ID orang dan nama depan mereka, dan daftar ID orang dan nama keluarga mereka. Beberapa orang tidak memiliki nama depan dan beberapa tidak memiliki nama keluarga; Saya ingin melakukan join luar penuh pada dua daftar.
Jadi daftar berikut ini:
ID FirstName
-- ---------
1 John
2 Sue
ID LastName
-- --------
1 Doe
3 Smith
Harus menghasilkan:
ID FirstName LastName
-- --------- --------
1 John Doe
2 Sue
3 Smith
Saya baru mengenal LINQ (jadi maafkan saya jika saya lumpuh) dan telah menemukan beberapa solusi untuk 'LINQ Outer Joins' yang semuanya terlihat sangat mirip, tetapi tampaknya benar-benar menjadi gabungan luar.
Upaya saya sejauh ini berlangsung seperti ini:
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;
}
Tapi ini kembali:
ID FirstName LastName
-- --------- --------
1 John Doe
2 Sue
Apa yang saya lakukan salah?
c#
.net
linq
outer-join
full-outer-join
ninjaPixel
sumber
sumber
Jawaban:
Saya tidak tahu apakah ini mencakup semua kasus, secara logis sepertinya benar. Idenya adalah untuk mengambil gabungan luar kiri dan bergabung luar kanan kemudian mengambil gabungan hasil.
Ini berfungsi seperti yang ditulis karena ada dalam LINQ to Objects. Jika LINQ ke SQL atau yang lain, prosesor kueri mungkin tidak mendukung navigasi yang aman atau operasi lainnya. Anda harus menggunakan operator kondisional untuk mendapatkan nilai secara kondisional.
yaitu,
sumber
AsEnumerable()
sebelum Anda melakukan penyatuan / penggabungan. Coba itu dan lihat bagaimana hasilnya. Jika ini bukan rute yang ingin Anda tuju, saya tidak yakin saya bisa membantu lebih dari itu.Pembaruan 1: menyediakan metode ekstensi yang benar-benar umum.
FullOuterJoin
Pembaruan 2: opsional menerima kebiasaan
IEqualityComparer
untuk jenis kunciPembaruan 3 : implementasi ini baru-baru ini menjadi bagian dari
MoreLinq
- Terima kasih kawan!Edit Added
FullOuterGroupJoin
( ideone ). Saya menggunakan kembaliGetOuter<>
implementasinya, menjadikan ini fraksi yang lebih sedikit performanya daripada yang seharusnya, tapi saya bertujuan untuk kode 'tingkat tinggi', bukan dioptimalkan, sekarang.Lihat langsung di http://ideone.com/O36nWc
Mencetak output:
Anda juga dapat menyediakan default: http://ideone.com/kG4kqO
Pencetakan:
Penjelasan istilah yang digunakan:
Bergabung adalah istilah yang dipinjam dari desain basis data relasional:
a
sebanyak ada unsur-unsur dalamb
dengan kunci yang sesuai (yaitu: apa-apa jikab
yang kosong). Database panggilan istilah iniinner (equi)join
.a
yang tidak ada unsur yang sesuai ada dib
. (yaitu: hasil bahkan jikab
kosong). Ini biasanya disebut sebagaileft join
.a
sertab
jika ada unsur yang sesuai ada yang lain. (yaitu hasil bahkan jikaa
kosong)Sesuatu yang biasanya tidak terlihat di RDBMS adalah grup bergabung [1] :
a
untuk beberapa yang sesuaib
, itu kelompok catatan dengan tombol yang sesuai. Ini sering lebih nyaman ketika Anda ingin menghitung melalui catatan 'bergabung', berdasarkan pada kunci umum.Lihat juga GroupJoin yang berisi beberapa penjelasan latar belakang umum juga.
[1] (Saya percaya Oracle dan MSSQL memiliki ekstensi untuk ini)
Kode lengkap
Kelas Ekstensi 'drop-in' umum untuk ini
sumber
FullOuterJoin
metode ekstensi yang disediakana.GroupBy(selectKeyA).ToDictionary();
sebagaia.ToLookup(selectKeyA)
danadict.OuterGet(key)
sebagaialookup[key]
. Mendapatkan koleksi kunci sedikit rumit, meskipun:alookup.Select(x => x.Keys)
.Saya pikir ada masalah dengan sebagian besar dari ini, termasuk jawaban yang diterima, karena mereka tidak bekerja dengan baik dengan Linq atas IQueryable baik karena melakukan bolak-balik server terlalu banyak dan terlalu banyak pengembalian data, atau melakukan terlalu banyak eksekusi klien.
Untuk IEnumerable, saya tidak suka jawaban Sehe atau serupa karena memiliki penggunaan memori yang berlebihan (tes daftar sederhana 10000000 menjalankan Linqpad kehabisan memori pada mesin 32GB saya).
Juga, sebagian besar yang lain tidak benar-benar menerapkan Full Outer Join yang tepat karena mereka menggunakan Union dengan Right Join daripada Concat dengan Right Anti Semi Join, yang tidak hanya menghilangkan duplikat inner join rows dari hasil, tetapi duplikat yang tepat yang ada awalnya di data kiri atau kanan.
Jadi di sini adalah ekstensi saya yang menangani semua masalah ini, menghasilkan SQL serta mengimplementasikan bergabung dalam LINQ ke SQL secara langsung, mengeksekusi di server, dan lebih cepat dan dengan memori lebih sedikit daripada yang lain di Enumerables:
Perbedaan antara Right Anti-Semi-Join sebagian besar diperdebatkan dengan Linq to Objects atau di sumbernya, tetapi membuat perbedaan di sisi server (SQL) dalam jawaban akhir, menghapus yang tidak perlu
JOIN
.Pengkodean tangan
Expression
untuk menangani penggabunganExpression<Func<>>
ke dalam lambda dapat ditingkatkan dengan LinqKit, tetapi akan lebih baik jika bahasa / kompiler menambahkan bantuan untuk itu. FungsiFullOuterJoinDistinct
danRightOuterJoin
termasuk untuk kelengkapan, tapi saya tidak menerapkan kembaliFullOuterGroupJoin
.Saya menulis versi lain untuk join luar penuh
IEnumerable
untuk kasus-kasus di mana kuncinya dapat dipesan, yaitu sekitar 50% lebih cepat daripada menggabungkan gabungan luar kiri dengan anti-bergabung kanan, setidaknya pada koleksi kecil. Ini melewati setiap koleksi setelah memilah hanya sekali.Saya juga menambahkan jawaban lain untuk versi yang bekerja dengan EF dengan mengganti
Invoke
dengan ekspansi kustom.sumber
TP unusedP, TC unusedC
? Apakah mereka benar-benar tidak digunakan?TP
,TC
,TResult
untuk menciptakan yang tepatExpression<Func<>>
. Aku seharusnya saya bisa menggantinya dengan_
,__
,___
bukan, tapi itu tampaknya tidak lebih jelas sampai C # memiliki wildcard parameter yang tepat untuk digunakan sebagai gantinya.The LINQ expression node type 'Invoke' is not supported in LINQ to Entities.
. Apakah ada batasan dengan kode ini? Saya ingin melakukan FULL JOIN atas IQueryablesInvoke
dengan kebiasaanExpressionVisitor
untuk inlineInvoke
sehingga harus bekerja dengan EF. Bisakah kamu mencobanya?Berikut adalah metode ekstensi yang melakukan itu:
sumber
Union
menghapus duplikat, jadi jika ada baris duplikat di data asli, mereka tidak akan di hasilnya.Saya menduga pendekatan @ sehe lebih kuat, tetapi sampai saya memahaminya dengan lebih baik, saya mendapati diri saya melompat-lompat dari ekstensi @ MichaelSander. Saya memodifikasinya agar cocok dengan sintaks dan mengembalikan tipe metode bawaan Enumerable.Join () yang dijelaskan di sini . Saya menambahkan sufiks "berbeda" sehubungan dengan komentar @ cadrell0 di bawah solusi @ JeffMercado.
Dalam contoh, Anda akan menggunakannya seperti ini:
Di masa depan, ketika saya belajar lebih banyak, saya merasa saya akan bermigrasi ke logika @hehe mengingat popularitasnya. Tetapi meskipun demikian saya harus berhati-hati, karena saya merasa penting untuk memiliki setidaknya satu kelebihan yang cocok dengan sintaksis dari metode ".Join ()" yang ada jika memungkinkan, karena dua alasan:
Saya masih baru dengan obat generik, ekstensi, pernyataan Func, dan fitur lainnya, jadi umpan balik tentu diterima.
EDIT: Tidak butuh waktu lama bagi saya untuk menyadari ada masalah dengan kode saya. Saya sedang melakukan .Dump () di LINQPad dan melihat jenis kembali. Itu hanya IEnumerable, jadi saya mencoba mencocokkannya. Tetapi ketika saya benar-benar melakukan .Where () atau .Pilih () pada ekstensi saya, saya mendapat kesalahan: "'System Collections.IEnumerable' tidak mengandung definisi untuk 'Pilih' dan ...". Jadi pada akhirnya saya bisa mencocokkan sintaks input .Gabung (), tetapi bukan perilaku pengembalian.
EDIT: Menambahkan "TResult" ke jenis kembali untuk fungsi. Kehilangan itu ketika membaca artikel Microsoft, dan tentu saja itu masuk akal. Dengan perbaikan ini, sekarang tampaknya perilaku pengembalian sejalan dengan tujuan saya.
sumber
Seperti yang Anda temukan, Linq tidak memiliki konstruksi "gabungan luar". Yang terdekat yang bisa Anda dapatkan adalah gabungan luar kiri menggunakan kueri yang Anda nyatakan. Untuk ini, Anda dapat menambahkan elemen apa pun dari daftar nama belakang yang tidak terwakili dalam bergabung:
sumber
Saya suka jawaban sehe, tetapi tidak menggunakan eksekusi yang ditunda (urutan input dengan bersemangat disebutkan oleh panggilan ke ToLookup). Jadi setelah melihat sumber .NET untuk objek -LINQ , saya datang dengan ini:
Implementasi ini memiliki sifat-sifat penting berikut:
Properti ini penting, karena itulah yang diharapkan seseorang dari FullOuterJoin tetapi berpengalaman dengan LINQ.
sumber
Saya memutuskan untuk menambahkan ini sebagai jawaban terpisah karena saya tidak yakin itu sudah cukup diuji. Ini adalah implementasi ulang dari
FullOuterJoin
metode yang menggunakan versiLINQKit
Invoke
/Expand
untukExpression
yang pada dasarnya disederhanakan dan disesuaikan sehingga harus bekerja dengan Entity Framework. Tidak ada banyak penjelasan karena hampir sama dengan jawaban saya sebelumnya.sumber
base.Visit(node)
tidak boleh melempar pengecualian karena itu hanya berulang turun pohon. Saya dapat mengakses hampir semua layanan berbagi kode, tetapi tidak menyiapkan database pengujian. Menjalankannya terhadap LINQ to SQL test saya tampaknya berfungsi dengan baik.Guid
kunci danGuid?
kunci asing?Melakukan enumerasi streaming dalam memori pada kedua input dan memanggil pemilih untuk setiap baris. Jika tidak ada korelasi pada iterasi saat ini, salah satu argumen pemilih akan menjadi nol .
Contoh:
Membutuhkan IComparer untuk tipe korelasi, menggunakan Comparer.Default jika tidak disediakan.
Mengharuskan 'OrderBy' diterapkan pada input enumerables
sumber
OrderBy
di kedua proyeksi utama.OrderBy
buffer seluruh urutan, karena alasan yang jelas .Solusi bersih saya untuk situasi yang penting adalah kunci di kedua enumerables:
begitu
output:
sumber
Gabung luar penuh untuk dua atau lebih tabel: Pertama, ekstrak kolom yang ingin Anda gabungkan.
Kemudian gunakan gabungan luar kiri antara kolom yang diekstraksi dan tabel utama.
sumber
Saya telah menulis kelas ekstensi ini untuk aplikasi mungkin 6 tahun yang lalu, dan telah menggunakannya sejak itu dalam banyak solusi tanpa masalah. Semoga ini bisa membantu.
sunting: Saya perhatikan beberapa mungkin tidak tahu cara menggunakan kelas ekstensi.
Untuk menggunakan kelas ekstensi ini, cukup referensi namespace-nya di kelas Anda dengan menambahkan baris berikut menggunakan joinext;
^ ini akan memungkinkan Anda untuk melihat intellisense fungsi ekstensi pada setiap koleksi objek IEnumerable yang kebetulan Anda gunakan.
Semoga ini membantu. Beri tahu saya jika masih belum jelas, dan saya harap saya akan menulis contoh contoh tentang cara menggunakannya.
Sekarang inilah kelasnya:
sumber
SelectMany
tidak dapat dikonversi ke pohon ekspresi LINQ2SQL-layak, tampaknya.Saya pikir bahwa LINQ join clause bukanlah solusi yang tepat untuk masalah ini, karena tujuan join clause bukanlah untuk mengakumulasi data sedemikian rupa seperti yang diperlukan untuk solusi tugas ini. Kode untuk menggabungkan koleksi terpisah yang dibuat menjadi terlalu rumit, mungkin itu OK untuk tujuan pembelajaran, tetapi tidak untuk aplikasi nyata. Salah satu cara untuk mengatasi masalah ini adalah dalam kode di bawah ini:
Jika koleksi asli besar untuk pembentukan HashSet, bukan foreach loop dapat menggunakan kode di bawah ini:
sumber
Terima kasih semuanya atas posting yang menarik!
Saya memodifikasi kode karena dalam kasus saya, saya perlu
Bagi yang berminat ini adalah kode saya yang dimodifikasi (dalam VB, maaf)
sumber
Namun bergabung dengan luar penuh lainnya
Karena tidak begitu senang dengan kesederhanaan dan keterbacaan proposisi lain, saya berakhir dengan ini:
Ia tidak memiliki pretensi untuk menjadi cepat (sekitar 800 ms untuk bergabung dengan 1000 * 1000 pada CPU 2020m: 2.4ghz / 2cores). Bagi saya, itu hanya gabungan luar yang ringkas dan kasual.
Ia bekerja sama dengan SQL FULL OUTER JOIN (duplikat konservasi)
Bersulang ;-)
Idenya adalah untuk
Berikut ini adalah tes singkat yang menyertainya:
Tempatkan break point di akhir untuk memverifikasi secara manual bahwa itu berperilaku seperti yang diharapkan
}
sumber
Saya sangat membenci ekspresi LINQ ini, inilah sebabnya SQL ada:
Buat ini sebagai tampilan sql dalam database dan impor sebagai entitas.
Tentu saja, gabungan (kiri) dari gabungan kiri dan kanan juga akan berhasil, tetapi itu bodoh.
sumber