2012-11-27 9 views
7

Ho una query LINQ che è composta da un oggetto anonimo.Costruire dinamicamente "o" query LIKE in LINQ su SQL

A un certo punto, voglio limitare i risultati per parametri di ricerca in entrata, ma questo può essere uno o più parametri, e voglio eseguire un "MI PIACE O MI PIACE O LIKE" usando quelli.

Nel codice, sarebbe simile a questa:

reservations = reservations.Where(r => 
    r.GuestLastName.Contains(parameter1) || r.GuestFirstName.Contains(parameter1) || 
    r.GuestLastName.Contains(parameter2) || r.GuestFirstName.Contains(parameter2) || 
    // Parameter 3, 4, 5,.. 
); 

Come avrei potuto costruire questo in modo dinamico, sapendo che reservations è del tipo IQueryable<'a> (anonymous object)? Ho dato un'occhiata alle varie risorse e posso solo trovare il modo di farlo quando conosco il tipo, non quando uso i tipi anonimi.

E 'importante sapere che si tratta di LINQ to SQL, quindi dovrebbe essere tradotto in una query SQL e non essere filtrati in memoria ...

risposta

2

Ci sono due modi possibili:

  1. Costruire un Expression, come sottolineato da Coincoin
  2. Mettere tutti i parametri in un array e utilizzando Any:

    var parameters = new [] { parameter1, parameter2, /*...*/ } 
    reservations = reservations 
        .Where(r => 
         parameters.Any(p => r.GuestFirstName.Contains(p) 
              || r.GuestLastName.Contains(p))); 
    
1

vorrei scrivere il mio metodo di estensione generico:

public static class CollectionHelper 
{ 
    public static IQueryable Filter<T>(this IQueryable source, string[] properties, string[] values) 
    { 
     var lambda = CombineLambdas<T>(properties, values); 
     var result = typeof (Queryable).GetMethods().First(
      method => method.Name == "Where" 
         && method.IsGenericMethodDefinition) 
             .MakeGenericMethod(typeof (T)) 
             .Invoke(null, new object[] {source, lambda}); 
     return (IQueryable<T>) result; 
    } 

    // combine lambda expressions using OR operator 
    private static LambdaExpression CombineLambdas<T>(string[] properties, string[] values) 
    { 
     var param = Expression.Parameter(typeof (T)); 
     LambdaExpression prev = null; 
     foreach (var value in values) 
     { 
      foreach (var property in properties) 
      { 
       LambdaExpression current = GetContainsExpression<T>(property, value); 
       if (prev != null) 
       { 
        Expression body = Expression.Or(Expression.Invoke(prev, param), 
                Expression.Invoke(current, param)); 
        prev = Expression.Lambda(body, param); 
       } 
       prev = prev ?? current; 
      } 
     } 
     return prev; 
    } 

    // construct expression tree to represent String.Contains 
    private static Expression<Func<T, bool>> GetContainsExpression<T>(string propertyName, string propertyValue) 
    { 
     var parameterExp = Expression.Parameter(typeof (T), "type"); 
     var propertyExp = Expression.Property(parameterExp, propertyName); 
     var method = typeof (string).GetMethod("Contains", new[] {typeof (string)}); 
     var someValue = Expression.Constant(propertyValue, typeof (string)); 
     var containsMethodExp = Expression.Call(propertyExp, method, someValue); 

     return Expression.Lambda<Func<T, bool>>(containsMethodExp, parameterExp); 
    } 
} 

e l'utilizzo:

var reservations = new List<TheType>() // sample collection 
    { 
     new TheType {FirstName = "aa", LastName = "bb"}, 
     new TheType {FirstName = "cc", LastName = "dd"}, 
     new TheType {FirstName = "ee", LastName = "ff"} 
    }.AsQueryable(); 

var filtered = reservations 
    .Filter<TheType>(new[] {"FirstName", "LastName"}, new[] {"d", "e"}); 
/* returnes 2 elements: 
* {FirstName = "cc", LastName = "dd"} and {FirstName = "ee", LastName = "ff"} */ 

non so una soluzione generale che si desidera avere - se esiste qualsiasi , ma spero che possa essere un'alternativa accettabile che risolva il tuo caso costruendo il filtro desiderato in modo dinamico.

0

ho trovato la soluzione dopo un po 'di debug, ma crea un WhereFilter con più selettori, uno per FirstName e uno per Cognome ..

Questo è il metodo di estensione:

public static IQueryable<T> WhereFilter<T>(this IQueryable<T> source, string[] possibleValues, params Expression<Func<T, string>>[] selectors) 
{ 
    List<Expression> expressions = new List<Expression>(); 

    var param = Expression.Parameter(typeof(T), "p"); 

    var bodies = new List<MemberExpression>(); 
    foreach (var s in selectors) 
    { 
     bodies.Add(Expression.Property(param, ((MemberExpression)s.Body).Member.Name)); 
    } 

    foreach (var v in possibleValues) 
    { 
     foreach(var b in bodies) { 
      expressions.Add(Expression.Call(b, "Contains", null, Expression.Constant(v))); 
     } 
    } 

    var finalExpression = expressions.Aggregate((accumulate, equal) => Expression.Or(accumulate, equal)); 

    return source.Where(Expression.Lambda<Func<T, bool>>(finalExpression, param)); 
} 

Può essere usato in questo modo:

reservations = reservations.WhereFilter(
    array_of_allowed_values, 
    r => r.GuestFirstName, 
    r => r.GuestLastName 
); 

ho controllato la stringa di traccia della query ed effettivamente convertiti in SQL, quindi il filtraggio avviene nel database.

+0

Questa soluzione richiede il passaggio di espressioni lambda alla firma della funzione. Per quanto ho capito la tua domanda, la tua esigenza era di costruirli dinamicamente. Ad esempio, considera un'azione invocata come risultato di una richiesta GET nell'ambiente WEB. Nei parametri di query i nomi delle proprietà da filtrare vengono inviati al server, insieme ai loro possibili valori. Nel tuo caso, sei costretto a costruire espressioni lambda basate sui nomi di proprietà che hai ricevuto dal client, invece di limitarti a passare alla funzione filtro. – jwaliszko