Warunkowe zapytania Linq

92

Pracujemy nad przeglądarką dziennika. Użycie będzie miało opcję filtrowania według użytkownika, ważności itp. W dniach Sql dodałbym do ciągu zapytania, ale chcę to zrobić za pomocą Linq. Jak mogę warunkowo dodać klauzule gdzie?

sgwill
źródło

Odpowiedzi:

156

jeśli chcesz filtrować tylko po spełnieniu określonych kryteriów, zrób coś takiego

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

Dzięki temu drzewo wyrażeń będzie dokładnie takie, jakie chcesz. W ten sposób utworzony SQL będzie dokładnie tym, czego potrzebujesz i niczym mniej.

Darren Kopp
źródło
2
Cześć Czy masz jakieś sugestie dotyczące tworzenia gdzie klauzule OR zamiast ORAZ ..?
Jon H,
1
Tak ... to trochę trudne. Najlepsze, co widziałem, to wzorzec specyfikacji i wciągnięcie predykatu do specyfikacji, a następnie wywołanie specyfikacji lub (someOtherSpecification). Zasadniczo musisz trochę napisać własne drzewo wyrażeń. Przykładowy kod i wyjaśnienie tutaj: codeinsanity.com/archive/2008/08/13/…
Darren Kopp
Mam głupie pytanie, czy jeśli te logi są pobierane z bazy danych, czy otrzymujemy wszystkie logi, a następnie filtrujemy je w pamięci? Jeśli tak, to w jaki sposób mogę przekazać warunki do bazy danych
Ali Umair
nie filtruje ich w pamięci. tworzy zapytanie i wysyła wszystkie warunki w bazie danych (przynajmniej dla większości dostawców linq-to-x)
Darren Kopp
otrzymywanie tego błęduLINQ 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

Jeśli chcesz filtrować na podstawie listy / tablicy, użyj następujących:

    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
źródło
3
To zdecydowanie najlepsza i najbardziej poprawna odpowiedź. Warunkowe || porównuje tylko pierwszą część i pomija drugą, jeśli pierwsza część jest prawdziwa ... świetnie!
Serj Sagan
1
Ta konstrukcja zawiera część „lub” wyrażenia w wygenerowanym zapytaniu SQL. Zaakceptowana odpowiedź wygeneruje bardziej efektywne wypowiedzi. Oczywiście w zależności od optymalizacji dostawcy danych. LINQ-to-SQL może mieć lepszą optymalizację, ale LINQ-to-Entities nie.
Suncat2000
20

Skończyłem używając odpowiedzi podobnej do odpowiedzi Darena, ale z interfejsem IQueryable:

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

To tworzy zapytanie przed trafieniem do bazy danych. Polecenie nie zostanie uruchomione do końca .ToList ().

sgwill
źródło
14

Jeśli chodzi o linq warunkowy, bardzo lubię filtry i układ rur.
http://blog.wekeroad.com/mvc-storefront/mvcstore-part-3/

Zasadniczo tworzysz metodę rozszerzającą dla każdego przypadku filtru, który przyjmuje IQueryable i parametr.

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
źródło
8

Rozwiązałem to za pomocą metody rozszerzenia, aby umożliwić warunkowe włączenie LINQ w środku płynnego wyrażenia. Eliminuje to potrzebę dzielenia wyrażenia na ifinstrukcje.

.If() metoda rozszerzenia:

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

Dzięki temu możesz to zrobić:

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

Oto także IEnumerable<T>wersja, która będzie obsługiwać większość innych wyrażeń LINQ:

public static IEnumerable<TSource> If<TSource>(
    this IEnumerable<TSource> source,
    bool condition,
    Func<IEnumerable<TSource>, IEnumerable<TSource>> branch)
    {
        return condition ? branch(source) : source;
    }
Ryan
źródło
4

Inną opcją byłoby użycie omawianego tutaj PredicateBuildera . Pozwala pisać kod podobny do następującego:

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;

Zauważ, że mam to tylko do pracy z Linq 2 SQL. EntityFramework nie implementuje Expression.Invoke, co jest wymagane, aby ta metoda działała. Mam pytanie dotyczące tego problemu tutaj .

Brad Leach
źródło
Jest to świetna metoda dla osób korzystających z warstwy logiki biznesowej na szczycie repozytorium wraz z narzędziem takim jak AutoMapper do mapowania między obiektami transferu danych a modelami jednostek. Użycie konstruktora predykatów pozwoli Ci dynamicznie modyfikować IQueryable przed wysłaniem go do AutoMapper w celu spłaszczenia, czyli przeniesienia listy do pamięci. Zauważ, że obsługuje również Entity Framework.
chrisjsherm
3

Robiąc to:

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

mając to w whereoświadczeniu:

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

oznacza, że ​​po utworzeniu ostatniego zapytania, jeśli lastNameSearchjest, falsezapytanie całkowicie pominie dowolny kod SQL podczas wyszukiwania nazwiska.

James Livingston
źródło
Zależy od dostawcy danych. LINQ-to-Entities nie optymalizuje go tak dobrze.
Suncat2000
1

Nie jest to najpiękniejsza rzecz, ale możesz użyć wyrażenia lambda i opcjonalnie przekazać swoje warunki. W TSQL wykonuję wiele następujących czynności, aby parametry były opcjonalne:

WHERE Field = @FieldVar LUB @FieldVar IS NULL

Możesz zduplikować ten sam styl z następującą lambdą (przykład sprawdzania uwierzytelniania):

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
źródło
1

Ostatnio miałem podobne wymaganie i ostatecznie znalazłem to w MSDN. Przykłady CSharp dla programu Visual Studio 2008

Klasy zawarte w pobranym przykładzie DynamicQuery umożliwiają tworzenie dynamicznych zapytań w czasie wykonywania w następującym formacie:

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

Używając tego, możesz dynamicznie budować ciąg zapytania w czasie wykonywania i przekazywać go do metody 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
źródło
1

Możesz utworzyć i używać tej metody rozszerzenia

public static IQueryable<TSource> WhereIf<TSource>(this IQueryable<TSource> source, bool isToExecute, Expression<Func<TSource, bool>> predicate)
{
    return isToExecute ? source.Where(predicate) : source;
}
Gustavo
źródło
0

Po prostu użyj operatora && w C #:

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

Edycja: Ach, przeczytaj uważniej. Chciałeś wiedzieć, jak warunkowo dodawać dodatkowe klauzule. W takim razie nie mam pojęcia. :) To, co prawdopodobnie zrobiłbym, to po prostu przygotować kilka zapytań i wykonać właściwe, w zależności od tego, czego potrzebowałem.

Smerf
źródło
0

Możesz użyć metody zewnętrznej:

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

...

bool ConditionalCheck( typeofRec input ) {
    ...
}

To zadziałałoby, ale nie można go podzielić na drzewa wyrażeń, co oznacza, że ​​Linq to SQL uruchomiłoby kod kontrolny na każdym rekordzie.

Alternatywnie:

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

To może działać w drzewach wyrażeń, co oznacza, że ​​Linq do SQL zostanie zoptymalizowany.

Keith
źródło
0

Cóż, pomyślałem, że możesz umieścić warunki filtru na ogólnej liście predykatów:

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

W rezultacie powstaje lista zawierająca „ja”, „meyou” i „mow”.

Możesz to zoptymalizować, wykonując foreach z predykatami w zupełnie innej funkcji, która łączy wszystkie predykaty.

Jon Limjap
źródło