Query Linq condizionali


92

Stiamo lavorando a un visualizzatore di log. L'utilizzo avrà la possibilità di filtrare per utente, severità, ecc. Nei giorni Sql lo aggiungerei alla stringa di query, ma voglio farlo con Linq. Come posso aggiungere in modo condizionale le clausole where?

Risposte:


156

se vuoi filtrare solo se vengono passati determinati criteri, fai qualcosa di simile

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

In questo modo, il tuo albero delle espressioni sarà esattamente quello che desideri. In questo modo l'SQL creato sarà esattamente ciò di cui hai bisogno e niente di meno.


2
Ciao Hai qualche suggerimento per inserire le clausole dove OR invece di AND ..?
Jon H

1
Sì ... è un po 'difficile da fare. Il meglio che ho visto è attraverso il modello di specifica e inserendo il predicato nella specifica e quindi chiamando la specifica.Oppure (qualche altra specifica). Fondamentalmente devi scrivere un po 'il tuo albero delle espressioni. Codice di esempio e spiegazione qui: codeinsanity.com/archive/2008/08/13/…
Darren Kopp

Ho una domanda stupida, se questi registri vengono acquisiti dal database, stiamo ottenendo tutti i registri e quindi li filtriamo in memoria? In tal caso, come posso passare le condizioni al database
Ali Umair

non li sta filtrando nella memoria. sta creando una query e inviando tutte le condizioni nel database (almeno per la maggior parte dei provider linq-to-x)
Darren Kopp

ottenere questo erroreLINQ 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

Se è necessario filtrare la base su un elenco / array, utilizzare quanto segue:

    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
Questa è di gran lunga la risposta migliore e più corretta. Il condizionale || confronta solo la prima parte e salta la seconda se la prima parte è vera ... ben fatto!
Serj Sagan

1
Questo costrutto include la parte "o" dell'espressione nella query SQL generata. La risposta accettata genererà affermazioni più efficienti. A seconda delle ottimizzazioni del fornitore di dati, ovviamente. LINQ-to-SQL potrebbe avere una migliore ottimizzazione, ma LINQ-to-Entities no.
Suncat 2000

20

Ho finito con una risposta simile a quella di Daren, ma con un'interfaccia 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();

Questo costruisce la query prima di raggiungere il database. Il comando non verrà eseguito fino alla fine .ToList ().


14

Quando si tratta di linq condizionale, mi piacciono molto i filtri e il modello dei tubi.
http://blog.wekeroad.com/mvc-storefront/mvcstore-part-3/

Fondamentalmente si crea un metodo di estensione per ogni caso di filtro che accetta IQueryable e un parametro.

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

8

Ho risolto questo problema con un metodo di estensione per consentire a LINQ di essere abilitato in modo condizionale nel mezzo di un'espressione fluente. Ciò elimina la necessità di suddividere l'espressione con ifdichiarazioni.

.If() metodo di estensione:

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

Questo ti permette di fare questo:

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

Ecco anche una IEnumerable<T>versione che gestirà la maggior parte delle altre espressioni LINQ:

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

4

Un'altra opzione potrebbe essere quella di utilizzare qualcosa come il PredicateBuilder discusso qui . Ti consente di scrivere codice come il seguente:

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;

Nota che ho solo questo per funzionare con Linq 2 SQL. EntityFramework non implementa Expression.Invoke, necessario per il funzionamento di questo metodo. Ho una domanda su questo problema qui .


Questo è un ottimo metodo per coloro che utilizzano un livello di logica aziendale sopra il proprio repository insieme a uno strumento come AutoMapper per mappare tra oggetti di trasferimento dati e modelli di entità. L'utilizzo del generatore di predicati ti consentirà di modificare dinamicamente il tuo IQueryable prima di inviarlo ad AutoMapper per l'appiattimento, cioè per portare l'elenco in memoria. Tieni presente che supporta anche Entity Framework.
chrisjsherm

3

Facendo questo:

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

avendo questo nella wheredichiarazione:

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

significa che quando viene creata la query finale, se lastNameSearchè falsela query ometterà completamente qualsiasi SQL per la ricerca del cognome.


Dipende dal fornitore di dati. LINQ-to-Entities non lo ottimizza così bene.
Suncat2000

1

Non è la cosa più carina ma puoi usare un'espressione lambda e passare le tue condizioni facoltativamente. In TSQL eseguo molte delle seguenti operazioni per rendere i parametri opzionali:

WHERE Field = @FieldVar OR @FieldVar È NULL

Puoi duplicare lo stesso stile con il seguente lambda (un esempio di verifica dell'autenticazione):

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

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

}


1

Recentemente ho avuto un requisito simile e alla fine l'ho trovato in MSDN. Esempi di CSharp per Visual Studio 2008

Le classi incluse nell'esempio DynamicQuery del download consentono di creare query dinamiche in fase di esecuzione nel seguente formato:

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

Usando questo puoi costruire una stringa di query dinamicamente in fase di runtime e passarla al metodo Where ():

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

1

Puoi creare e utilizzare questo metodo di estensione

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

0

Basta usare l'operatore && di C #:

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

Modifica: Ah, ho bisogno di leggere con più attenzione. Volevi sapere come aggiungere in modo condizionale clausole aggiuntive. In tal caso, non ne ho idea. :) Quello che probabilmente farei è semplicemente preparare diverse query ed eseguire quella giusta, a seconda di ciò di cui ho bisogno.


0

Potresti usare un metodo esterno:

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

...

bool ConditionalCheck( typeofRec input ) {
    ...
}

Questo funzionerebbe, ma non può essere suddiviso in alberi di espressione, il che significa che Linq to SQL eseguirà il codice di controllo su ogni record.

In alternativa:

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

Ciò potrebbe funzionare negli alberi delle espressioni, il che significa che Linq to SQL sarebbe ottimizzato.


0

Bene, quello che pensavo fosse che potresti inserire le condizioni del filtro in un elenco generico di predicati:

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

Il risultato è un elenco contenente "me", "meyou" e "mow".

Potresti ottimizzarlo eseguendo il foreach con i predicati in una funzione completamente diversa che esegue l'OR di tutti i predicati.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.