Koşullu Linq Sorguları


92

Günlük Görüntüleyici üzerinde çalışıyoruz. Kullanım, kullanıcıya, önem derecesine, vb. Göre filtreleme seçeneğine sahip olacaktır. Sql günlerinde sorgu dizesine eklerdim, ancak bunu Linq ile yapmak istiyorum. Koşullu olarak nerede cümleleri ekleyebilirim?

Yanıtlar:


156

yalnızca belirli kriterler geçilirse filtrelemek istiyorsanız, bunun gibi bir şey yapın

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

Bunu bu şekilde yapmak, İfade ağacınızın tam olarak istediğiniz gibi olmasını sağlayacaktır. Bu şekilde oluşturulan SQL tam olarak ihtiyacınız olan şey olacak ve daha azı olmayacak.


2
Merhaba AND'ler yerine where cümlelerini VEYA'ları yapmak için herhangi bir öneriniz var mı?
Jon H

1
Evet ... yapması biraz zor. Gördüğüm en iyi şey, spesifikasyon kalıbı ve yüklemi spesifikasyona çekmek ve ardından spesifikasyonu çağırmak veya (someOtherSpecification). Temel olarak biraz kendi ifade ağacınızı yazmanız gerekiyor. Örnek kod ve açıklama burada: codeinsanity.com/archive/2008/08/13/…
Darren Kopp

Aptalca bir sorum var, eğer bu günlükler veritabanından alınıyorsa, tüm günlükleri alıp bellekte filtreliyor muyuz? Öyleyse koşulları veritabanına nasıl aktarabilirim
Ali Umair

onları bellekte filtrelemiyor. bir sorgu oluşturuyor ve veritabanındaki tüm koşulları gönderiyor (en azından çoğu linq-to-x sağlayıcısı için)
Darren Kopp

bu hatayı alıyorumLINQ 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

Bir Liste / Dizide tabanı filtrelemeniz gerekirse, aşağıdakileri kullanın:

    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();

    }

3
Bu, açık ara en iyi ve en doğru cevaptır. Koşullu || sadece ilk bölümü karşılaştırır ve ilk bölüm doğruysa ikinciyi atlar ... güzelce yapılmış!
Serj Sagan

1
Bu yapı, üretilen SQL sorgusundaki ifadenin 'veya' kısmını içerir. Kabul edilen cevap, daha verimli ifadeler üretecektir. Veri sağlayıcısının optimizasyonlarına bağlı olarak elbette. LINQ-to-SQL daha iyi optimizasyona sahip olabilir, ancak LINQ-to-Entities'de yoktur.
Suncat2000

20

Daren'in yanıtına benzer, ancak bir IQueryable arayüzüyle bitirdim:

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();

Bu, veritabanına ulaşmadan önce sorguyu oluşturur. Komut, sonunda .ToList () olana kadar çalışmayacaktır.


14

Koşullu bağlantı söz konusu olduğunda, filtre ve boru modeline çok düşkünüm.
http://blog.wekeroad.com/mvc-storefront/mvcstore-part-3/

Temel olarak, IQueryable ve bir parametre alan her filtre durumu için bir uzantı yöntemi oluşturursunuz.

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

8

LINQ'nun akıcı bir ifadenin ortasında koşullu olarak etkinleştirilmesine izin vermek için bunu bir uzatma yöntemiyle çözdüm. Bu, ifadeyi ifadelerle bölme ihtiyacını ortadan kaldırır if.

.If() uzatma yöntemi:

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

Bu, bunu yapmanızı sağlar:

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

Burada ayrıca IEnumerable<T>diğer LINQ ifadelerinin çoğunu işleyecek bir sürüm bulunmaktadır:

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

4

Diğer bir seçenek de, burada tartışılan PredicateBuilder gibi bir şey kullanmak olabilir . Aşağıdaki gibi kod yazmanıza izin verir:

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;

Bunu yalnızca Linq 2 SQL ile çalışmak için aldığımı unutmayın. EntityFramework, bu yöntemin çalışması için gerekli olan Expression.Invoke uygulamaz. Burada bu konuyla ilgili bir sorum var .


Bu, veri aktarım nesneleri ve Varlık modelleri arasında eşleştirmek için AutoMapper gibi bir araçla birlikte depolarının üstünde bir İş Mantığı Katmanı kullananlar için harika bir yöntemdir. Yüklem oluşturucuyu kullanmak, IQueryable'ınızı düzleştirmek için, yani listeyi belleğe getirmek için AutoMapper'a göndermeden önce dinamik olarak değiştirmenize olanak sağlar. Ayrıca Entity Framework'ü desteklediğini unutmayın.
chrisjsherm

3

Bunu yapmak:

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

whereifadede buna sahip olmak :

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

araçlar bu son sorgu oluşturulduğunda, eğer lastNameSearcholduğunu falsesorgu tamamen soyadı arama için herhangi bir SQL ihmal edecek.


Veri sağlayıcısına bağlıdır. LINQ-to-Entities, onu o kadar iyi optimize etmez.
Suncat2000

1

En güzel şey değil ama bir lambda ifadesi kullanabilir ve isteğe bağlı olarak koşullarınızı geçebilirsiniz. TSQL'de parametreleri isteğe bağlı yapmak için aşağıdakilerin çoğunu yaparım:

WHERE Field = @FieldVar VEYA @FieldVar NULL IS

Aynı stili aşağıdaki lambda ile çoğaltabilirsiniz (kimlik doğrulamasını kontrol etmeye bir örnek):

MyDataContext db = new MyDataContext ();

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

Func checkUser = kullanıcı =>

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

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

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

Kullanıcı foundUser = db.Users.SingleOrDefault (checkUser);

}


1

Yakın zamanda benzer bir gereksinimim vardı ve sonunda bunu MSDN'de buldum. Visual Studio 2008 için CSharp Örnekleri

İndirmenin DynamicQuery örneğinde bulunan sınıflar, çalışma zamanında aşağıdaki biçimde dinamik sorgular oluşturmanıza olanak tanır:

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

Bunu kullanarak çalışma zamanında dinamik olarak bir sorgu dizesi oluşturabilir ve bunu Where () yöntemine iletebilirsiniz:

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

1

Bu uzantı yöntemini oluşturabilir ve kullanabilirsiniz

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

0

Sadece C # 's && operatörünü kullanın:

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

Düzenleme: Ah, daha dikkatli okumalısın. Koşullu olarak nasıl ek maddeler ekleyeceğinizi öğrenmek istediniz . Bu durumda hiçbir fikrim yok. :) Muhtemelen yapacağım şey birkaç sorgu hazırlamak ve neye ihtiyaç duyduğuma bağlı olarak doğru olanı çalıştırmaktır.


0

Harici bir yöntem kullanabilirsiniz:

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

...

bool ConditionalCheck( typeofRec input ) {
    ...
}

Bu işe yarar, ancak ifade ağaçlarına bölünemez, bu da Linq'den SQL'e kontrol kodunu her kayıt için çalıştıracağı anlamına gelir.

Alternatif olarak:

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

Bu ifade ağaçlarında işe yarayabilir, yani Linq to SQL optimize edilecektir.


0

Pekala, filtre koşullarını genel bir Predicates listesine koyabileceğinizi düşündüm:

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

Bu, "ben", "meyou" ve "biçme" kelimelerini içeren bir listeyle sonuçlanır.

Tahminlerle foreach'i, tüm yüklemleri OR'lu hale getiren tamamen farklı bir işlevde yaparak bunu optimize edebilirsiniz.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.