2010-09-23 10 views
5

(una precedente interrogazione, Recursively (?) compose LINQ predicates into a single predicate, è simile a questo, ma in realtà ho fatto la domanda sbagliata ... la soluzione non ha soddisfatto la questione posta, ma non è in realtà quello ho bisogno sono diversi, anche se onesto)Compose LINQ to SQL predicati in un unico predicato

Dato il seguente ricerca:...

"keyword1 keyword2 ... keywordN" 

voglio finire con il seguente SQL:

SELECT [columns] FROM Customer 
    WHERE (
     Customer.Forenames LIKE '%keyword1%' 
     OR 
     Customer.Forenames LIKE '%keyword2%' 
     OR 
     ... 
     OR 
     Customer.Forenames LIKE '%keywordN%' 
    ) AND (
     Customer.Surname LIKE '%keyword1%' 
     OR 
     Customer.Surname LIKE '%keyword2%' 
     OR 
     .... 
     OR 
     Customer.Surname LIKE '%keywordN%' 
    ) 

In effetti, stiamo dividendo il testo di ricerca sugli spazi, rifilatura ogni token, la costruzione di un multi-parte clausola OR sulla base di ciascuno, e quindi AND'ing le clausole insieme.

Lo sto facendo in Linq-to-SQL e non ho idea di come comporre dinamicamente un predicato basato su un elenco arbitrario di subpredicati. Per un numero noto di clausole, è facile comporre i predicati manualmente:

dataContext.Customers.Where(
    ( 
     Customer.Forenames.Contains("keyword1") 
     || 
     Customer.Forenames.Contains("keyword2") 
    ) && (
     Customer.Surname.Contains("keyword1") 
     || 
     Customer.Surname.Contains("keyword2") 
    ) 
); 

In breve, ho bisogno di una tecnica che, dati due predicati, restituirà un singolo predicato comporre i due sorgente predicati con un operatore fornito, ma limitato agli operatori esplicitamente supportati da Linq-to-SQL. Qualche idea?

+0

Questo potrebbe essere un duplicato di: http://stackoverflow.com/questions/180405/how-do-you-add-dynamic-where-clauses-to-a-linq-query – devuxer

+0

La query non ha molto senso ... Si vuole trovare clienti in cui il * nome * contiene almeno uno dei termini di ricerca e * Cognome * contiene almeno uno dei termini di ricerca, ecc.? La query non dovrebbe trovare i clienti che hanno * tutti * i termini di ricerca, ma in * qualsiasi * campo? – Timwi

risposta

6

È possibile utilizzare la classe di PredicateBuilder

IQueryable<Customer> SearchCustomers (params string[] keywords) 
{ 
    var predicate = PredicateBuilder.False<Customer>(); 

    foreach (string keyword in keywords) 
    { 
    // Note that you *must* declare a variable inside the loop 
    // otherwise all your lambdas end up referencing whatever 
    // the value of "keyword" is when they're finally executed. 
    string temp = keyword; 
    predicate = predicate.Or (p => p.Forenames.Contains (temp)); 
    } 
    return dataContext.Customers.Where (predicate); 
} 

(che in realtà è l'esempio dalla pagina PredicateBuilder, ho solo adattato al tuo caso ...)


EDIT:

In realtà ho letto male il tuo QUESTI su, e il mio esempio di cui sopra solo copre una parte della soluzione ... Il seguente metodo dovrebbe fare quello che vuoi:

IQueryable<Customer> SearchCustomers (string[] forenameKeyWords, string[] surnameKeywords) 
{ 
    var predicate = PredicateBuilder.True<Customer>(); 

    var forenamePredicate = PredicateBuilder.False<Customer>(); 
    foreach (string keyword in forenameKeyWords) 
    { 
     string temp = keyword; 
     forenamePredicate = forenamePredicate.Or (p => p.Forenames.Contains (temp)); 
    } 
    predicate = PredicateBuilder.And(forenamePredicate); 

    var surnamePredicate = PredicateBuilder.False<Customer>(); 
    foreach (string keyword in surnameKeyWords) 
    { 
     string temp = keyword; 
     surnamePredicate = surnamePredicate.Or (p => p.Surnames.Contains (temp)); 
    } 
    predicate = PredicateBuilder.And(surnamePredicate); 

    return dataContext.Customers.Where(predicate); 
} 

È possibile utilizzare in quel modo:

var query = SearchCustomers(
    new[] { "keyword1", "keyword2" }, 
    new[] { "keyword3", "keyword4" }); 

foreach (var Customer in query) 
{ 
    ... 
} 
+0

Genius. Molto obbligato. –

0

Normalmente si dovrebbe invocazioni catena di .Where(...). Es .:

var a = dataContext.Customers; 
if (kwd1 != null) 
    a = a.Where(t => t.Customer.Forenames.Contains(kwd1)); 
if (kwd2 != null) 
    a = a.Where(t => t.Customer.Forenames.Contains(kwd2)); 
// ... 
return a; 

LINQ to SQL sarebbe saldare tutto insieme in un unico WHERE clausola.

Questo non funziona con OR, però. È possibile utilizzare le unioni e le intersezioni, ma non sono sicuro se LINQ to SQL (o SQL Server) è abbastanza intelligente da piega di nuovo a un unico WHERE clausola. OTOH, non importa se le prestazioni non soffrono. In ogni caso, sarebbe simile a questa:

<The type of dataContext.Customers> ff = null, ss = null; 

foreach (k in keywords) { 
    if (keywords != null) { 
     var f = dataContext.Customers.Where(t => t.Customer.Forenames.Contains(k)); 
     ff = ff == null ? f : ff.Union(f); 

     var s = dataContext.Customers.Where(t => t.Customer.Surname.Contains(k)); 
     ss = ss == null ? s : ss.Union(s); 
    } 
} 
return ff.Intersect(ss); 
+0

Come si fa o usando quello? –

+0

Ah, stupido. Non ho prestato attenzione al '||' s. –

+0

Ho modificato la mia risposta. 'PredicateBuilder' sembra una soluzione migliore, ma lascerò il mio qui per ogni evenienza. –