2010-09-23 12 views
5

(Una pregunta anterior, Recursively (?) compose LINQ predicates into a single predicate, es similar a esto, pero en realidad hice la pregunta incorrecta ... la solución allí satisfizo la pregunta tal como se planteó, pero en realidad no es lo necesito son diferentes, aunque honesto)Componer predicados LINQ-to-SQL en un solo predicado

Dada la siguiente búsqueda de texto:...

"keyword1 keyword2 ... keywordN" 

quiero terminar con el siguiente 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%' 
    ) 

Efectivamente, estamos dividiendo el texto de búsqueda en espacios, recortando cada token, construyendo una cláusula O de varias partes basada en cada uno, y luego AND 'las cláusulas juntas.

Estoy haciendo esto en Linq-to-SQL, y no tengo idea de cómo componer dinámicamente un predicado basado en una lista larga arbitrariamente de subpredicados. Para un número conocido de cláusulas, es fácil para componer los predicados de forma manual:

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

En resumen, necesito una técnica que, dadas dos predicados, devolverá un solo predicado componer los dos fuente de predicados con un operador suministrado, pero restringido a los operadores explícitamente compatibles con Linq-to-SQL. ¿Algunas ideas?

+0

Esto podría ser un duplicado de: http://stackoverflow.com/questions/180405/how-do-you-add-dynamic-where-clauses-to-a-linq-query – devuxer

+0

Su consulta realmente no tiene sentido ... Quiere encontrar clientes donde * Forename * contiene al menos uno de los términos de búsqueda, y * Apellido * contiene al menos uno de los términos de búsqueda, etc. ¿No debería la consulta encontrar clientes que tienen * todos * los términos de búsqueda, pero en el campo * any *? – Timwi

Respuesta

6

Usted puede utilizar la clase 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); 
} 

(que en realidad es el ejemplo de la página PredicateBuilder, acabo adaptado a su caso ...)


EDIT:

En realidad leí mal el questi en adelante, y mi ejemplo anterior sólo cubre una parte de la solución ... El siguiente método debe hacer lo que quiera:

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

Usted puede usarlo como esa:

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

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

Genius. Muy agradecido. –

0

Normalmente, cadena de invocaciones de .Where(...). Ej .:

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 a SQL sería soldar todo de nuevo juntos en una sola cláusula WHERE.

Esto no funciona con OR, sin embargo. Podría usar uniones e intersecciones, pero no estoy seguro de si LINQ-to-SQL (o SQL Server) es lo suficientemente inteligente como para volver a doblarlo a una sola cláusula WHERE. OTOH, no importará si el rendimiento no sufre. De todos modos, se vería algo como esto:

<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

¿Cómo lo haces O usando eso? –

+0

Ah, tonto. No le presté atención a los '||' s. –

+0

He modificado mi respuesta. 'PredicateBuilder' parece una solución mejor, pero dejaré la mía aquí por las dudas. –