Linq to Entities - SQL “IN” klausa

230

Di T-SQL Anda bisa memiliki pertanyaan seperti:

SELECT * FROM Users WHERE User_Rights IN ("Admin", "User", "Limited")

Bagaimana Anda mereplikasi itu dalam permintaan LINQ ke Entitas? Apakah itu mungkin?

StevenMcD
sumber

Jawaban:

349

Anda harus mengaktifkannya sesuai dengan cara Anda memikirkannya. Alih-alih melakukan "dalam" untuk menemukan hak pengguna item saat ini di set yang telah ditentukan sebelumnya dari hak pengguna yang berlaku, Anda meminta seperangkat hak pengguna yang telah ditentukan sebelumnya jika mengandung nilai yang berlaku item saat ini. Ini persis cara yang sama Anda akan menemukan item dalam daftar reguler di .NET.

Ada dua cara untuk melakukan ini menggunakan LINQ, satu menggunakan sintaks kueri dan yang lainnya menggunakan sintaks metode. Pada dasarnya, keduanya sama dan dapat digunakan bergantian tergantung pada preferensi Anda:

Sintaks Kueri:

var selected = from u in users
               where new[] { "Admin", "User", "Limited" }.Contains(u.User_Rights)
               select u

foreach(user u in selected)
{
    //Do your stuff on each selected user;
}

Sintaks Metode:

var selected = users.Where(u => new[] { "Admin", "User", "Limited" }.Contains(u.User_Rights));

foreach(user u in selected)
{
    //Do stuff on each selected user;
}

Preferensi pribadi saya dalam contoh ini mungkin sintaksis metode karena alih-alih menetapkan variabel, saya bisa melakukan foreach melalui panggilan anonim seperti ini:

foreach(User u in users.Where(u => new [] { "Admin", "User", "Limited" }.Contains(u.User_Rights)))
{
    //Do stuff on each selected user;
}

Secara sintaksis ini terlihat lebih rumit, dan Anda harus memahami konsep ekspresi lambda atau delegasi untuk benar-benar mengetahui apa yang terjadi, tetapi seperti yang Anda lihat, ini memadatkan kode dalam jumlah yang wajar.

Semuanya bermuara pada gaya dan preferensi pengkodean Anda - ketiga contoh saya melakukan hal yang sama sedikit berbeda.

Cara alternatif bahkan tidak menggunakan LINQ, Anda dapat menggunakan sintaks metode yang sama menggantikan "di mana" dengan "FindAll" dan mendapatkan hasil yang sama, yang juga akan bekerja di .NET 2.0:

foreach(User u in users.FindAll(u => new [] { "Admin", "User", "Limited" }.Contains(u.User_Rights)))
{
    //Do stuff on each selected user;
}
BenAlabaster
sumber
mungkin saya terlalu cepat untuk menandai sebagai jawaban, tetapi saya tidak mendapatkan. Berisi setelah {"Admin", "Pengguna", "Terbatas"} VS2008 tidak suka kode itu sedikit pun.
StevenMcD
1
sesuai dengan nama saya "FailBoy" Saya menemukan jawabannya: PI dimasukkan ke dalam string [] dan kemudian menggunakannya dan berhasil. Terima kasih!
StevenMcD
maaf, saya lupa membuat anonim array yang baru;) Saya memperbaiki contoh kode saya. Senang Anda menemukan jawabannya sendiri.
BenAlabaster
28
Jawaban ini pasti benar seandainya pertanyaannya tentang Linq-to-SQL atau Linq secara umum. Namun, karena secara khusus tertulis "Linq-to-Entities", jawaban ini salah. array.Contains belum (belum) didukung oleh Linq-to-Entities.
KristoferA
6
@KristoferA - itu mungkin benar untuk versi EF yang lebih lama, tetapi sepertinya baik bagi saya dengan EF4.
Drew Noakes
21

Ini harus mencukupi tujuan Anda. Ini membandingkan dua koleksi dan memeriksa apakah satu koleksi memiliki nilai yang cocok dengan koleksi lainnya

fea_Features.Where(s => selectedFeatures.Contains(s.feaId))
Balaji Birajdar
sumber
8

Saya akan mengikuti Inner Join dalam konteks ini. Jika saya akan menggunakan isi, itu akan berulang 6 kali meskipun jika fakta bahwa hanya ada satu pertandingan.

var desiredNames = new[] { "Pankaj", "Garg" }; 

var people = new[]  
{  
    new { FirstName="Pankaj", Surname="Garg" },  
    new { FirstName="Marc", Surname="Gravell" },  
    new { FirstName="Jeff", Surname="Atwood" }  
}; 

var records = (from p in people join filtered in desiredNames on p.FirstName equals filtered  select p.FirstName).ToList(); 

Kekurangan dari Contains

Misalkan saya memiliki dua objek daftar.

List 1      List 2
  1           12
  2            7
  3            8
  4           98
  5            9
  6           10
  7            6

Menggunakan Contains, itu akan mencari setiap item Daftar 1 dalam Daftar 2 yang berarti iterasi akan terjadi 49 kali !!!

Pankaj
sumber
5
Ini benar-benar mengabaikan fakta bahwa pernyataan tersebut diterjemahkan ke dalam SQL. Lihat di sini .
Gert Arnold
5

Ini bisa menjadi cara yang memungkinkan Anda dapat secara langsung menggunakan metode ekstensi LINQ untuk memeriksa klausa in

var result = _db.Companies.Where(c => _db.CurrentSessionVariableDetails.Select(s => s.CompanyId).Contains(c.Id)).ToList();
Torakami
sumber
2

Saya juga mencoba untuk bekerja dengan SQL-IN-like hal - permintaan terhadap Model Data Entitas . Pendekatan saya adalah pembangun string untuk menyusun ekspresi OR besar. Itu sangat jelek, tapi aku khawatir itu satu-satunya cara untuk pergi sekarang.

Nah, itu terlihat seperti ini:

Queue<Guid> productIds = new Queue<Guid>(Products.Select(p => p.Key));
if(productIds.Count > 0)
{
    StringBuilder sb = new StringBuilder();
    sb.AppendFormat("{0}.ProductId = Guid\'{1}\'", entities.Products.Name, productIds.Dequeue());
    while(productIds.Count > 0)
    {
        sb.AppendFormat(" OR {0}.ProductId = Guid\'{1}\'",
          entities.Products.Name, productIds.Dequeue());
    }
}

Bekerja dengan GUID dalam konteks ini : Seperti yang Anda lihat di atas, selalu ada kata "GUID" di depan GUID jika ada dalam fragmen string kueri. Jika Anda tidak menambahkan ini, ObjectQuery<T>.Whereberikan pengecualian berikut:

Jenis argumen 'Edm.Guid' dan 'Edm.String' tidak kompatibel untuk operasi ini., Hampir sama dengan ekspresi, baris 6, kolom 14.

Menemukan ini di Forum MSDN, mungkin bermanfaat untuk diingat.

Matthias

... menantikan versi .NET dan Entity Framework berikutnya, ketika semuanya menjadi lebih baik. :)

Matthias Meid
sumber
2

Metode alternatif untuk jawaban BenAlabaster

Pertama-tama, Anda dapat menulis ulang kueri seperti ini:

var matches = from Users in people
        where Users.User_Rights == "Admin" ||
              Users.User_Rights == "Users" || 
              Users.User_Rights == "Limited"
        select Users;

Tentu saja ini lebih 'bertele-tele' dan menyusahkan untuk ditulis tetapi berfungsi semua sama.

Jadi jika kita memiliki beberapa metode utilitas yang membuatnya mudah untuk membuat ekspresi LINQ semacam ini, kita akan berada dalam bisnis.

dengan metode utilitas di tempat Anda dapat menulis sesuatu seperti ini:

var matches = ctx.People.Where(
        BuildOrExpression<People, string>(
           p => p.User_Rights, names
        )
);

Ini membangun ekspresi yang memiliki efek yang sama dengan:

var matches = from p in ctx.People
        where names.Contains(p.User_Rights)
        select p;

Tetapi yang lebih penting sebenarnya bekerja melawan .NET 3.5 SP1.

Berikut adalah fungsi plumbing yang memungkinkan ini:

public static Expression<Func<TElement, bool>> BuildOrExpression<TElement, TValue>(
        Expression<Func<TElement, TValue>> valueSelector, 
        IEnumerable<TValue> values
    )
{     
    if (null == valueSelector) 
        throw new ArgumentNullException("valueSelector");

    if (null == values)
        throw new ArgumentNullException("values");  

    ParameterExpression p = valueSelector.Parameters.Single();

    if (!values.Any())   
        return e => false;

    var equals = values.Select(value =>
        (Expression)Expression.Equal(
             valueSelector.Body,
             Expression.Constant(
                 value,
                 typeof(TValue)
             )
        )
    );
   var body = equals.Aggregate<Expression>(
            (accumulate, equal) => Expression.Or(accumulate, equal)
    ); 

   return Expression.Lambda<Func<TElement, bool>>(body, p);
}

Saya tidak akan mencoba menjelaskan metode ini, selain mengatakan itu pada dasarnya membangun ekspresi predikat untuk semua nilai menggunakan valueSelector (yaitu p => p.User_Rights) dan ATAU predikat itu bersama-sama untuk membuat ekspresi untuk lengkap predikat

Sumber: http://blogs.msdn.com/b/alexj/archive/2009/03/26/tip-8-writing-where-in-style-queries-using-linq-to-entities.aspx

api di lubang
sumber
0

Contoh nyata:

var trackList = Model.TrackingHistory.GroupBy(x => x.ShipmentStatusId).Select(x => x.Last()).Reverse();
List<int> done_step1 = new List<int>() {2,3,4,5,6,7,8,9,10,11,14,18,21,22,23,24,25,26 };
bool isExists = trackList.Where(x => done_step1.Contains(x.ShipmentStatusId.Value)).FirstOrDefault() != null;
Adel Mourad
sumber
-14

Serius? Kalian tidak pernah menggunakan

where (t.MyTableId == 1 || t.MyTableId == 2 || t.MyTableId == 3)
cjm30305
sumber
9
-1 Cobalah ini dengan 20 nilai atau lebih dalam tabel dengan lebih dari 1000 baris dan Anda akan dengan cepat melihat keuntungan dari solusi yang diterima. Juga, tidak mudah untuk menambahkan sejumlah kondisi yang berubah-ubah ke pernyataan where (seperti jika pengguna memilih untuk menyertakan opsi 1 dan 2, tetapi tidak 3).
Ditanggalkan
Yah, saya tidak memerlukan hal-hal yang gila dari ilmuwan dan jawaban ini saya pilih karena saya membutuhkan AND dan 2 ORS var SamplePoints = (dari c di _db.tblPWS_WSF_SPID_ISN_Lookup.OrderBy (x => x.WSFStateCode) di mana c. PWS == id && ((c.WSFStateCode.Substring (0, 2) == "SR") || (c.WSFStateCode.Substring (0, 2) == "CH")) pilih c) .ToList () ;
JustJohn
@Trisped - jumlah baris (1000) tidak mengubah apa pun - atau apakah saya kehilangan sesuatu?
tymtam
@ Timski Ya, jumlah baris penting. Semakin banyak baris, semakin banyak perhitungan. Sama dengan jumlah nilai yang mungkin: Checks = NumValues * NumRows. Karena ini adalah perhitungan tipe M * N, jika salah satu kecil, maka waktu untuk melakukan setiap pemeriksaan yang diperlukan juga akan kecil. Saya menambahkan kendala sehingga cjm30305 akan tahu cara mengatur lingkungan pengujian di mana menunjukkan mengapa solusinya buruk.
Ditebangi
@Trisped Apakah Anda mengatakan bahwa where new[] { 1, 2, 3 }.Contains(x)perbandingannya kurang where (x == 1 || x == 2 || x == 3)?
tymtam