Pilih beberapa catatan berdasarkan daftar Id dengan LINQ


122

Saya memiliki daftar yang berisi Id dari UserProfiletabel saya . Bagaimana cara memilih semua UserProfilesberdasarkan daftar ID yang saya vargunakan LINQ?

var idList = new int[1, 2, 3, 4, 5];
var userProfiles = _dataContext.UserProfile.Where(......);

Saya terjebak di sini. Saya bisa melakukan ini menggunakan for loop dll. Tapi saya lebih suka melakukan ini dengan LINQ.


4
mencari dan menemukan adalah 2 hal yang berbeda. Tetapi karena Anda dapat melihat ke belakang melalui internet, dapatkah Anda memberi tahu saya bagaimana Anda tahu saya tidak mencari? tunggu jangan bilang! Anda melihatnya dengan benar? maksud saya persis.
Yustme

5
mengajukan pertanyaan membutuhkan lebih banyak waktu daripada melakukan pencarian. lain kali anggap saja 'dia' melakukan pencarian atau 10.
Yustme

2
Ini masih mendapat sedikit perhatian, jadi saya pikir saya akan menyebutkan bahwa ReSharper melakukan pekerjaan yang sangat baik dalam menyarankan tempat di mana Anda dapat mengubah kode iteratif menjadi pernyataan LINQ. Bagi orang yang baru mengenal LINQ, ini bisa menjadi alat yang sangat diperlukan untuk memiliki tujuan ini sendiri.
Yuck

Jawaban:


206

Anda bisa menggunakannya Contains()untuk itu. Ini akan terasa sedikit mundur ketika Anda benar-benar mencoba menghasilkan INklausa, tetapi ini harus dilakukan:

var userProfiles = _dataContext.UserProfile
                               .Where(t => idList.Contains(t.Id));

Saya juga berasumsi bahwa setiap UserProfilerecord akan memiliki int Idfield. Jika bukan itu masalahnya, Anda harus menyesuaikannya.


Hai, ya, catatan profil pengguna berisi id. Jadi entah bagaimana saya akan melakukan sesuatu seperti t => t.id == idList.Contains (id)?
Yustme

Contains()akan menangani pemeriksaan persamaan itu pada setiap idnilai jika Anda menggunakannya seperti yang saya tulis di jawaban. Anda tidak perlu menulis secara eksplisit di ==mana pun saat mencoba membandingkan item dari satu set (larik) dengan yang lain (tabel database).
Yuck

Masalahnya adalah bahwa t menampung seluruh objek UserProfile, dan idList hanya berisi int. Kompiler mengeluh tentang sesuatu tetapi saya telah berhasil memperbaikinya. Terima kasih.
Yustme

2
@Yuck - Tidak berfungsi untuk saya, Says Function timed out! Telah menonaktifkan pemuatan lambat tetapi masih gagal.
bhuvin

1
Saya mendapatkan "Tidak dapat mengubah ekspresi lambda menjadi 'int' karena ini bukan tipe delegasi". Bagaimana cara memperbaikinya?
Stian

92

Solusi dengan .Where dan .Contains memiliki kompleksitas O (N square). Sederhana .Join harus memiliki kinerja yang jauh lebih baik (mendekati O (N) karena hashing). Jadi kode yang benar adalah:

_dataContext.UserProfile.Join(idList, up => up.ID, id => id, (up, id) => up);

Dan sekarang hasil pengukuran saya. Saya menghasilkan 100.000 ProfilPengguna dan 100.000 id. Bergabung mengambil 32ms dan. Dimana dengan .Contains membutuhkan waktu 2 menit dan 19 detik! Saya menggunakan IEnumerable murni untuk pengujian ini untuk membuktikan pernyataan saya. Jika Anda menggunakan List, bukan IEnumerable, .Where dan .Contains akan lebih cepat. Pokoknya perbedaannya signifikan. Yang tercepat. Di mana. Berisi adalah dengan Set <>. Semua itu tergantung pada kompleksitas kolom yang mendasari untuk .Contains. Lihat posting ini untuk mempelajari tentang kompleksitas LINQ. Lihat contoh pengujian saya di bawah ini:

    private static void Main(string[] args)
    {
        var userProfiles = GenerateUserProfiles();
        var idList = GenerateIds();
        var stopWatch = new Stopwatch();
        stopWatch.Start();
        userProfiles.Join(idList, up => up.ID, id => id, (up, id) => up).ToArray();
        Console.WriteLine("Elapsed .Join time: {0}", stopWatch.Elapsed);
        stopWatch.Restart();
        userProfiles.Where(up => idList.Contains(up.ID)).ToArray();
        Console.WriteLine("Elapsed .Where .Contains time: {0}", stopWatch.Elapsed);
        Console.ReadLine();
    }

    private static IEnumerable<int> GenerateIds()
    {
       // var result = new List<int>();
        for (int i = 100000; i > 0; i--)
        {
            yield return i;
        }
    }

    private static IEnumerable<UserProfile> GenerateUserProfiles()
    {
        for (int i = 0; i < 100000; i++)
        {
            yield return new UserProfile {ID = i};
        }
    }

Keluaran konsol:

Berlalu. Bergabung waktu: 00: 00: 00.0322546

Berlalu Dimana. Berisi waktu: 00: 02: 19.4072107


4
Bisakah Anda mendukungnya dengan angka?
Yustme

Nice, bagaimanapun, membuat saya penasaran dengan timing apa yang akan digunakan saat Listdigunakan. +1
Yustme

Oke, berikut adalah pengaturan waktu yang Anda minati: Daftar memakan waktu 13,1 detik dan HashSet membutuhkan 0,7 md! Jadi .Where .Contains adalah yang terbaik hanya dalam kasus HashSet (ketika .Contains memiliki kompleksitas O (1)). Dalam kasus lain, .Join lebih baik
David Gregor

5
Saya mendapatkan Local sequence cannot be used in LINQ to SQL implementations of query operators except the Contains operator.error saat menggunakan LINQ2SQL datacontext.
Mayank Raichura

3
@Yustme - kinerja selalu menjadi pertimbangan. (Saya benci menjadi orang "ini harus menjadi jawaban yang diterima", tapi ...)
jleach

19

Jawaban bagus di atas, tapi jangan lupakan satu hal PENTING - mereka memberikan hasil yang berbeda!

  var idList = new int[1, 2, 2, 2, 2]; // same user is selected 4 times
  var userProfiles = _dataContext.UserProfile.Where(e => idList.Contains(e)).ToList();

Ini akan mengembalikan 2 baris dari DB (dan ini mungkin benar, jika Anda hanya ingin daftar pengguna yang disortir berbeda)

TETAPI dalam banyak kasus, Anda mungkin menginginkan daftar hasil yang tidak diurutkan . Anda harus selalu memikirkannya seperti kueri SQL. Silakan lihat contoh dengan keranjang belanja eshop untuk menggambarkan apa yang terjadi:

  var priceListIDs = new int[1, 2, 2, 2, 2]; // user has bought 4 times item ID 2
  var shoppingCart = _dataContext.ShoppingCart
                     .Join(priceListIDs, sc => sc.PriceListID, pli => pli, (sc, pli) => sc)
                     .ToList();

Ini akan mengembalikan 5 hasil dari DB. Dalam kasus ini, menggunakan 'berisi' akan menjadi salah.


13

Itu seharusnya sederhana. Coba ini:

var idList = new int[1, 2, 3, 4, 5];
var userProfiles = _dataContext.UserProfile.Where(e => idList.Contains(e));
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.