Query Linq Bersyarat

92

Kami sedang mengerjakan Log Viewer. Penggunaan akan memiliki opsi untuk memfilter berdasarkan pengguna, tingkat keparahan, dll. Pada hari Sql saya akan menambahkan ke string kueri, tetapi saya ingin melakukannya dengan Linq. Bagaimana cara menambahkan klausa where secara kondisional?

sgwill
sumber

Jawaban:

156

jika Anda hanya ingin memfilter jika kriteria tertentu terlampaui, lakukan sesuatu seperti ini

var logs = from log in context.Logs
           select log;

if (filterBySeverity)
    logs = logs.Where(p => p.Severity == severity);

if (filterByUser)
    logs = logs.Where(p => p.User == user);

Melakukannya dengan cara ini akan memungkinkan pohon Ekspresi Anda menjadi persis seperti yang Anda inginkan. Dengan cara itu SQL yang dibuat akan menjadi apa yang Anda butuhkan dan tidak kurang.

Darren Kopp
sumber
2
Hai Apakah Anda punya saran untuk membuat klausa where bukan ANDs ..?
Jon H
1
Ya ... itu agak sulit untuk dilakukan. Yang terbaik yang pernah saya lihat adalah melalui pola spesifikasi dan menarik predikat ke dalam spesifikasi dan kemudian memanggil spesifikasi. Atau (someOtherSpecification). Pada dasarnya Anda harus menulis sedikit pohon ekspresi Anda sendiri. Contoh kode dan penjelasannya di sini: codeinsanity.com/archive/2008/08/13/…
Darren Kopp
Saya punya pertanyaan bodoh, Jika log ini diperoleh dari database, apakah kita mendapatkan semua log dan kemudian memfilternya di memori? Jika demikian, maka bagaimana saya bisa meneruskan kondisi ke database
Ali Umair
itu tidak memfilter mereka dalam memori. itu membangun kueri dan mengirimkan semua kondisi dalam database (setidaknya untuk sebagian besar penyedia linq-ke-x)
Darren Kopp
mendapatkan kesalahan iniLINQ to Entities does not recognize the method 'System.String get_Item(System.String)' method, and this method cannot be translated into a store expression.
Ali Umair
23

Jika Anda perlu memfilter berdasarkan List / Array, gunakan yang berikut ini:

    public List<Data> GetData(List<string> Numbers, List<string> Letters)
    {
        if (Numbers == null)
            Numbers = new List<string>();

        if (Letters == null)
            Letters = new List<string>();

        var q = from d in database.table
                where (Numbers.Count == 0 || Numbers.Contains(d.Number))
                where (Letters.Count == 0 || Letters.Contains(d.Letter))
                select new Data
                {
                    Number = d.Number,
                    Letter = d.Letter,
                };
        return q.ToList();

    }
Carlos
sumber
3
Sejauh ini, ini adalah jawaban terbaik dan paling benar. Kondisional || hanya membandingkan bagian pertama dan melewatkan bagian kedua jika bagian pertama benar ... bagus sekali!
Serj Sagan
1
Konstruksi ini mencakup bagian 'atau' dari ekspresi dalam kueri SQL yang dihasilkan. Jawaban yang diterima akan menghasilkan pernyataan yang lebih efisien. Bergantung pada optimasi penyedia data, tentu saja. LINQ-to-SQL mungkin memiliki pengoptimalan yang lebih baik, tetapi LINQ-to-Entities tidak.
Suncat2000
20

Saya akhirnya menggunakan jawaban yang mirip dengan Daren, tetapi dengan antarmuka IQuerable:

IQueryable<Log> matches = m_Locator.Logs;

// Users filter
if (usersFilter)
    matches = matches.Where(l => l.UserName == comboBoxUsers.Text);

 // Severity filter
 if (severityFilter)
     matches = matches.Where(l => l.Severity == comboBoxSeverity.Text);

 Logs = (from log in matches
         orderby log.EventTime descending
         select log).ToList();

Itu membangun kueri sebelum mencapai database. Perintah tidak akan berjalan hingga .ToList () di akhir.

sgwill
sumber
14

Ketika berbicara tentang linq bersyarat, saya sangat menyukai pola filter dan pipa.
http://blog.wekeroad.com/mvc-storefront/mvcstore-part-3/

Pada dasarnya Anda membuat metode ekstensi untuk setiap kasus filter yang menggunakan IQuerable dan parameter.

public static IQueryable<Type> HasID(this IQueryable<Type> query, long? id)
{
    return id.HasValue ? query.Where(o => i.ID.Equals(id.Value)) : query;
}
Lars Mæhlum
sumber
8

Saya menyelesaikan ini dengan metode ekstensi untuk memungkinkan LINQ diaktifkan secara bersyarat di tengah ekspresi fasih. Ini menghilangkan kebutuhan untuk memecah ekspresi dengan ifpernyataan.

.If() metode ekstensi:

public static IQueryable<TSource> If<TSource>(
        this IQueryable<TSource> source,
        bool condition,
        Func<IQueryable<TSource>, IQueryable<TSource>> branch)
    {
        return condition ? branch(source) : source;
    }

Ini memungkinkan Anda melakukan ini:

return context.Logs
     .If(filterBySeverity, q => q.Where(p => p.Severity == severity))
     .If(filterByUser, q => q.Where(p => p.User == user))
     .ToList();

Ini juga IEnumerable<T>versi yang akan menangani sebagian besar ekspresi LINQ lainnya:

public static IEnumerable<TSource> If<TSource>(
    this IEnumerable<TSource> source,
    bool condition,
    Func<IEnumerable<TSource>, IEnumerable<TSource>> branch)
    {
        return condition ? branch(source) : source;
    }
Ryan
sumber
4

Pilihan lain adalah menggunakan sesuatu seperti PredicateBuilder yang dibahas di sini . Ini memungkinkan Anda untuk menulis kode seperti berikut:

var newKids  = Product.ContainsInDescription ("BlackBerry", "iPhone");

var classics = Product.ContainsInDescription ("Nokia", "Ericsson")
                  .And (Product.IsSelling());

var query = from p in Data.Products.Where (newKids.Or (classics))
            select p;

Perhatikan bahwa saya hanya mendapatkan ini untuk bekerja dengan Linq 2 SQL. EntityFramework tidak mengimplementasikan Expression.Invoke, yang diperlukan agar metode ini berfungsi. Saya punya pertanyaan tentang masalah ini di sini .

Brad Leach
sumber
Ini adalah metode yang bagus bagi mereka yang menggunakan Business Logic Layer di atas repositori mereka bersama dengan alat seperti AutoMapper untuk memetakan antara objek transfer data dan model Entitas. Menggunakan pembuat predikat akan memungkinkan Anda untuk secara dinamis memodifikasi IQuerable Anda sebelum mengirimnya ke AutoMapper untuk diratakan, yaitu membawa daftar ke dalam memori. Perhatikan bahwa itu juga mendukung Entity Framework.
chrisjsherm
3

Melakukan ini:

bool lastNameSearch = true/false; // depending if they want to search by last name,

memiliki ini dalam wherepernyataan:

where (lastNameSearch && name.LastNameSearch == "smith")

berarti bahwa ketika permintaan akhir dibuat, jika lastNameSearchadalah falsequery-benar akan menghilangkan SQL apapun untuk pencarian nama terakhir.

James Livingston
sumber
Tergantung pada penyedia data. LINQ-to-Entities tidak mengoptimalkannya dengan baik.
Suncat2000
1

Ini bukan hal tercantik tetapi Anda dapat menggunakan ekspresi lambda dan meneruskan kondisi Anda secara opsional. Di TSQL saya melakukan banyak hal berikut untuk membuat parameter opsional:

DI MANA Bidang = @FieldVar ATAU @FieldVar IS NULL

Anda dapat menduplikasi gaya yang sama dengan lambda berikut (contoh pemeriksaan otentikasi):

MyDataContext db = new MyDataContext ();

void RunQuery (string param1, string param2, int? param3) {

Func checkUser = user =>

((param1.Length> 0)? user.Param1 == param1: 1 == 1) &&

((param2.Length> 0)? user.Param2 == param2: 1 == 1) &&

((param3! = null)? user.Param3 == param3: 1 == 1);

User foundUser = db.Users.SingleOrDefault (checkUser);

}

t3rse
sumber
1

Saya memiliki persyaratan serupa baru-baru ini dan akhirnya menemukan ini di MSDN dia. CSharp Sampel untuk Visual Studio 2008

Class yang disertakan dalam sampel DynamicQuery download memungkinkan Anda membuat kueri dinamis saat runtime dalam format berikut:

var query =
db.Customers.
Where("City = @0 and Orders.Count >= @1", "London", 10).
OrderBy("CompanyName").
Select("new(CompanyName as Name, Phone)");

Dengan menggunakan ini, Anda bisa membuat string kueri secara dinamis saat runtime dan meneruskannya ke metode Where ():

string dynamicQueryString = "City = \"London\" and Order.Count >= 10"; 
var q = from c in db.Customers.Where(queryString, null)
        orderby c.CompanyName
        select c;
Andy Rose
sumber
1

Anda dapat membuat dan menggunakan metode ekstensi ini

public static IQueryable<TSource> WhereIf<TSource>(this IQueryable<TSource> source, bool isToExecute, Expression<Func<TSource, bool>> predicate)
{
    return isToExecute ? source.Where(predicate) : source;
}
Gustavo
sumber
0

Cukup gunakan operator C # &&:

var items = dc.Users.Where(l => l.Date == DateTime.Today && l.Severity == "Critical")

Edit: Ah, perlu dibaca lebih teliti. Anda ingin tahu cara menambahkan klausa tambahan secara bersyarat . Kalau begitu, saya tidak tahu. :) Yang mungkin saya lakukan hanyalah menyiapkan beberapa kueri, dan menjalankan kueri yang benar, tergantung pada apa yang akhirnya saya perlukan.

TheSmurf
sumber
0

Anda bisa menggunakan metode eksternal:

var results =
    from rec in GetSomeRecs()
    where ConditionalCheck(rec)
    select rec;

...

bool ConditionalCheck( typeofRec input ) {
    ...
}

Ini akan berhasil, tetapi tidak dapat dipecah menjadi pohon ekspresi, yang berarti Linq ke SQL akan menjalankan kode pemeriksaan terhadap setiap record.

Kalau tidak:

var results =
    from rec in GetSomeRecs()
    where 
        (!filterBySeverity || rec.Severity == severity) &&
        (!filterByUser|| rec.User == user)
    select rec;

Itu mungkin bekerja di pohon ekspresi, yang berarti Lini ke SQL akan dioptimalkan.

Keith
sumber
0

Nah, yang saya pikirkan adalah Anda bisa memasukkan kondisi filter ke dalam daftar Predikat umum:

    var list = new List<string> { "me", "you", "meyou", "mow" };

    var predicates = new List<Predicate<string>>();

    predicates.Add(i => i.Contains("me"));
    predicates.Add(i => i.EndsWith("w"));

    var results = new List<string>();

    foreach (var p in predicates)
        results.AddRange(from i in list where p.Invoke(i) select i);               

Itu menghasilkan daftar yang berisi "me", "meyou", dan "mow".

Anda dapat mengoptimalkannya dengan melakukan foreach dengan predikat dalam fungsi yang sangat berbeda dari OR semua predikat.

Jon Limjap
sumber