2008-08-14 5 views
78

Pracujemy nad Log Viewer. Użycie będzie mieć opcję filtrowania według użytkownika, poziomu ważności itp. W dniach Sql dodałem do ciągu zapytania, ale chcę to zrobić z Linq. Jak mogę warunkowo dodać klauzule where?Warunkowe zapytania Linq

Odpowiedz

138

jeśli chcesz tylko przefiltrować, jeśli pewne kryteria są przekazywane, zrobić 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); 

Doing więc w ten sposób pozwoli drzewo Expression się dokładnie, co chcesz. W ten sposób stworzony SQL będzie dokładnie tym, czego potrzebujesz i niczym mniej.

+2

Witam Czy masz jakieś sugestie co do wprowadzenia klauzul WHO zamiast OR? –

+1

Tak ... to trochę trudne. Najlepsze, co widziałem, to poprzez wzorzec specyfikacji i wyciągnięcie predykatu do specyfikacji, a następnie wywołanie specyfikacji.Or (someOtherSpecification). Zasadniczo trzeba trochę napisać własne drzewo ekspresji. Przykładowy kod i wyjaśnienie tutaj: http://codeinsanity.com/archive/2008/08/13/implementing-repository-and-specification-patterns-using-linq.aspx –

+0

Mam głupie pytanie, jeśli te dzienniki są pozyskiwane 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 –

0

Wystarczy użyć C# 's & & operatora:

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

EDIT: Ah, trzeba bardziej uważnie przeczytać. Chcesz wiedzieć, jak warunkowo dodać dodatkowe punkty. W takim razie nie mam pojęcia. :) Najprawdopodobniej po prostu przygotuję kilka zapytań i wykonam właściwą, w zależności od tego, czego potrzebowałem.

0

Można użyć metody zewnętrzne:

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

... 

bool ConditionalCheck(typeofRec input) { 
    ... 
} 

to będzie działać, ale nie może być podzielone na drzewach ekspresji, co oznacza, LINQ to SQL byłoby uruchomić kod sprawdzić przed 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ć na drzewach ekspresji, czyli LINQ do SQL byłyby zoptymalizowane.

0

No, co myślałem, że można umieścić warunki filtr do ogólnej listy 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);    

Wynika to w wykazie zawierającym „mnie”, „meyou” i „kosić”.

Można to zoptymalizować, wykonując foreach z predykatami w zupełnie innej funkcji niż wszystkie predykaty.

1

Nie jest to najładniejsza rzecz, ale można użyć wyrażenia lambda i opcjonalnie przekazać warunki. W TSQL zrobić wiele z następujących czynności, aby parametry opcjonalne:

WHERE pole = @FieldVar LUB @FieldVar IS NULL

Można powielać ten sam styl z następujących lambda (przykład sprawdzania autentykacji):

MyDataContext db = new MyDataContext();

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

Func Checkuser = user =>

((param1.Length> 0)? Użytkownika.Param1 == param1: 1 == 1) & &

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

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

Użytkownik znalazł użytkownika = db.Users.SingleOrDefault (checkUser);

}

13

Jeśli chodzi o LINQ warunkowego, jestem bardzo lubiący filtrów i rur wzorca.
http://blog.wekeroad.com/mvc-storefront/mvcstore-part-3/

Zasadniczo tworzysz metodę rozszerzenia dla każdego przypadku filtra, który pobiera 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; 
} 
3

Inną opcją byłoby użyć coś takiego PredicateBuilder omówione here. To pozwala na pisanie kodu tak:

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; 

uwaga, że ​​mam to tylko do pracy z LINQ 2 SQL. EntityFramework nie implementuje wyrażenia Invion.Invoke, które jest wymagane do działania tej metody. Mam pytanie dotyczące tego problemu: here.

+0

To jest świetna metoda dla osób używających Business Logic Layer na szczycie ich repozytorium wraz z narzędziem takim jak AutoMapper do mapowania pomiędzy transferem danych obiekty i modele encji. Korzystanie z kreatora predykatów pozwala na dynamiczną modyfikację swojego IQueryable przed wysłaniem go do AutoMappera w celu spłaszczenia, tj. Przeniesienia listy do pamięci. Należy pamiętać, że obsługuje on także strukturę Entity. – chrisjsherm

1

Miałem podobne wymagania ostatnio i ostatecznie znalazłem to w jego MSDN. CSharp Samples for Visual Studio 2008

Klasy zawarte w próbce DynamicQuery do pobrania pozwala na 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)"); 

Korzystanie może zbudować ciąg kwerendy dynamicznie w czasie wykonywania i przekazać go w czym() metoda:

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

skończyło używając odpowiedź podobną do Daren, 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 buduje kwerendę przed pójściem do bazy danych. Polecenie nie będzie działać do końca .ToList().

21

Jeśli trzeba filtrować bazę na liście/Array użyć 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(); 

    } 
+3

To zdecydowanie najlepsza i najbardziej poprawna odpowiedź. Warunkowe || porównuje tylko pierwszą część i pomija drugą, jeśli pierwsza część jest prawdziwa ... ładnie wykonana! –

+0

Ta konstrukcja zawiera "lub" część wyrażenia w wygenerowanym zapytaniu SQL. Przyjęta odpowiedź wygeneruje bardziej wydajne stwierdzenia. Oczywiście w zależności od optymalizacji dostawcy danych. LINQ-SQL może mieć lepszą optymalizację, ale LINQ-to-Entities nie. – Suncat2000

3

ten sposób:

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

Mając to na rachunku where:

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

oznacza, że ​​po utworzeniu zapytania końcowego, jeślijest false zapytanie całkowicie pominie wszelkie zapytania SQL dla wyszukiwania według nazwiska.

+0

W zależności od dostawcy danych. LINQ-to-Entities nie optymalizuje go tak dobrze. – Suncat2000