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?

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.


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

    }

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.


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

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

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 .


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.


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

}}


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;

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

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.


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.


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.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.