Bedingte Linq-Abfragen

92

Wir arbeiten an einem Log Viewer. Die Verwendung hat die Option, nach Benutzer, Schweregrad usw. zu filtern. In den SQL-Tagen würde ich der Abfragezeichenfolge hinzufügen, aber ich möchte dies mit Linq tun. Wie kann ich where-Klauseln bedingt hinzufügen?

sgwill
quelle

Antworten:

156

Wenn Sie nur filtern möchten, wenn bestimmte Kriterien erfüllt sind, gehen Sie wie folgt vor

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

Auf diese Weise kann Ihr Ausdrucksbaum genau das sein, was Sie möchten. Auf diese Weise ist das erstellte SQL genau das, was Sie brauchen, und nicht weniger.

Darren Kopp
quelle
2
Hallo Haben Sie Vorschläge, wie Sie die where-Klauseln ODERs anstelle von UNDs setzen können?
Jon H
1
Ja ... es ist ein bisschen schwierig zu tun. Das Beste, was ich gesehen habe, ist, durch das Spezifikationsmuster das Prädikat in die Spezifikation zu ziehen und dann die Spezifikation aufzurufen. Oder (someOtherSpecification). Grundsätzlich müssen Sie Ihren eigenen Ausdrucksbaum ein wenig schreiben. Beispielcode und Erklärung hier: codeinsanity.com/archive/2008/08/13/…
Darren Kopp
Ich habe eine dumme Frage: Wenn diese Protokolle aus der Datenbank erfasst werden, erhalten wir dann alle Protokolle und filtern sie dann im Speicher? Wenn ja, wie kann ich dann die Bedingungen an die Datenbank übergeben
Ali Umair
Es filtert sie nicht im Speicher. Es baut eine Abfrage auf und sendet alle Bedingungen in der Datenbank (zumindest für die meisten Linq-to-X-Anbieter)
Darren Kopp
diesen Fehler bekommenLINQ 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
22

Wenn Sie die Basis nach einer Liste / einem Array filtern müssen, verwenden Sie Folgendes:

    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
quelle
3
Dies ist bei weitem die beste und richtigste Antwort. Die Bedingung || vergleicht nur den ersten Teil und überspringt den zweiten, wenn der erste Teil wahr ist ... gut gemacht!
Serj Sagan
1
Dieses Konstrukt enthält den Teil 'oder' des Ausdrucks in der generierten SQL-Abfrage. Die akzeptierte Antwort generiert effizientere Aussagen. Natürlich abhängig von den Optimierungen des Datenanbieters. LINQ-to-SQL bietet möglicherweise eine bessere Optimierung, LINQ-to-Entities jedoch nicht.
Suncat2000
20

Ich beendete die Verwendung einer Antwort ähnlich der von Daren, jedoch mit einer IQueryable-Oberfläche:

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

Dadurch wird die Abfrage aufgebaut, bevor die Datenbank aufgerufen wird. Der Befehl wird erst am Ende von .ToList () ausgeführt.

sgwill
quelle
14

Wenn es um bedingte Linq geht, mag ich das Filter- und Rohrmuster sehr.
http://blog.wekeroad.com/mvc-storefront/mvcstore-part-3/

Grundsätzlich erstellen Sie eine Erweiterungsmethode für jeden Filterfall, der IQueryable und einen Parameter enthält.

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
quelle
7

Ich habe dies mit einer Erweiterungsmethode gelöst, damit LINQ in der Mitte eines fließenden Ausdrucks bedingt aktiviert werden kann. Dadurch entfällt die Notwendigkeit, den Ausdruck mit ifAnweisungen aufzubrechen .

.If() Erweiterungsmethode:

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

Dies ermöglicht Ihnen Folgendes:

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

Hier ist auch eine IEnumerable<T>Version, die die meisten anderen LINQ-Ausdrücke verarbeitet:

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

Eine andere Möglichkeit wäre, etwas wie den hier diskutierten PredicateBuilder zu verwenden . Sie können Code wie folgt schreiben:

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;

Beachten Sie, dass dies nur für Linq 2 SQL geeignet ist. EntityFramework implementiert nicht Expression.Invoke, das erforderlich ist, damit diese Methode funktioniert. Ich habe eine Frage bezüglich dieses Problems hier .

Brad Leach
quelle
Dies ist eine großartige Methode für Benutzer, die einen Business Logic Layer über ihrem Repository zusammen mit einem Tool wie AutoMapper verwenden, um Datenübertragungsobjekte und Entitätsmodelle zuzuordnen. Wenn Sie den Prädikaten-Generator verwenden, können Sie Ihr IQueryable dynamisch ändern, bevor Sie es zum Reduzieren an AutoMapper senden, dh die Liste in den Speicher bringen. Beachten Sie, dass es auch Entity Framework unterstützt.
Chrisjsherm
3

Dies tun:

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

mit diesem in der whereAussage:

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

bedeutet , dass , wenn die endgültige Abfrage erstellt, wenn lastNameSearchist falsedie Abfrage vollständig jede SQL für den Nachnamen Suche auslassen wird.

James Livingston
quelle
Hängt vom Datenprovider ab. LINQ-to-Entities optimiert es nicht so gut.
Suncat2000
1

Es ist nicht die schönste Sache, aber Sie können einen Lambda-Ausdruck verwenden und Ihre Bedingungen optional übergeben. In TSQL mache ich viele der folgenden Schritte, um Parameter optional zu machen:

WHERE Field = @FieldVar ODER @FieldVar IST NULL

Sie können denselben Stil mit dem folgenden Lambda duplizieren (ein Beispiel für die Überprüfung der Authentifizierung):

MyDataContext db = new MyDataContext ();

void RunQuery (Zeichenfolge param1, Zeichenfolge 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);

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

}}

t3rse
quelle
1

Ich hatte kürzlich eine ähnliche Anforderung und fand diese schließlich in der MSDN. CSharp-Beispiele für Visual Studio 2008

Mit den im DynamicQuery-Beispiel des Downloads enthaltenen Klassen können Sie zur Laufzeit dynamische Abfragen im folgenden Format erstellen:

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

Auf diese Weise können Sie eine Abfragezeichenfolge zur Laufzeit dynamisch erstellen und an die Where () -Methode übergeben:

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
quelle
1

Sie können diese Erweiterungsmethode erstellen und verwenden

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

Verwenden Sie einfach den && Operator von C #:

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

Edit: Ah, muss genauer lesen. Sie wollten wissen, wie Sie zusätzliche Klauseln bedingt hinzufügen können. In diesem Fall habe ich keine Ahnung. :) Was ich wahrscheinlich tun würde, ist einfach mehrere Abfragen vorzubereiten und die richtige auszuführen, je nachdem, was ich letztendlich brauchte.

Der Schlumpf
quelle
0

Sie können eine externe Methode verwenden:

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

...

bool ConditionalCheck( typeofRec input ) {
    ...
}

Dies würde funktionieren, kann aber nicht in Ausdrucksbäume unterteilt werden, was bedeutet, dass Linq to SQL den Prüfcode für jeden Datensatz ausführen würde.

Alternative:

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

Dies könnte in Ausdrucksbäumen funktionieren, was bedeutet, dass Linq to SQL optimiert wird.

Keith
quelle
0

Ich dachte, Sie könnten die Filterbedingungen in eine allgemeine Liste von Prädikaten aufnehmen:

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

Das Ergebnis ist eine Liste mit "Ich", "Ich" und "Mähen".

Sie können dies optimieren, indem Sie foreach mit den Prädikaten in einer völlig anderen Funktion ausführen, die alle Prädikate ODER-verknüpft.

Jon Limjap
quelle