2009-06-05 10 views
14

Ho creato un BindingList filtrabile from this source. Funziona alla grande:Come creare un System.Linq.Expressions.Expression per Like?

list.Filter("Customer == 'Name'"); 

fa quello che dovrebbe. Gli interni funzionano come un parser, che converte l'espressione == o != in System.Linq.Expressions.Expression. In questo caso, == diventa System.Linq.Expressions.Expression.Equal.

Sfortunatamente System.Linq.Expressions.Expression non contiene un operatore simile e non so come risolvere questo problema.

Il codice iniziale è simile al seguente:

private static Dictionary<String, Func<Expression, Expression, Expression>> 
    binaryOpFactory = new Dictionary<String, Func<Expression, Expression, Expression>>(); 

static Init() { 
    binaryOpFactory.Add("==", Expression.Equal); 
    binaryOpFactory.Add(">", Expression.GreaterThan); 
    binaryOpFactory.Add("<", Expression.LessThan); 
    binaryOpFactory.Add(">=", Expression.GreaterThanOrEqual); 
    binaryOpFactory.Add("<=", Expression.LessThanOrEqual); 
    binaryOpFactory.Add("!=", Expression.NotEqual); 
    binaryOpFactory.Add("&&", Expression.And); 
    binaryOpFactory.Add("||", Expression.Or); 
} 

Poi ho creato un'espressione che farà quello che voglio:

private static System.Linq.Expressions.Expression<Func<String, String, bool>> 
    Like_Lambda = (item, search) => item.ToLower().Contains(search.ToLower()); 

private static Func<String, String, bool> Like = Like_Lambda.Compile(); 

esempio

Console.WriteLine(like("McDonalds", "donAld")); // true 
Console.WriteLine(like("McDonalds", "King")); // false 

Ma binaryOpFactory richiede questo:

Func<Expression, Expression, Expression> 

Le espressioni predefinite sembrano essere esattamente questo:

System.Linq.Expressions.Expression.Or; 

Qualcuno può dirmi come convertire la mia espressione?

+0

E come il vostro simile opera? Posso aiutarti a creare un'espressione, ma ho bisogno di capire come vuoi che funzioni prima ... regex? contiene? eccetera? –

+0

Non importa. L'implementazione finale sarà propiziamente con regexp. Fondamentalmente ho un Func a cui passo 2 Strings e ottengo true o false come returnvalue. Il mio problema è che non capisco l'implementazione di oggetti in System.Linq.Expressions.Expression Namespace, che sembrano essere Func (guarda i generici argements di binaryOpFactory) quindi non posso creare il mio confronto. –

+0

Re commento: la comprensione dell'API di Expression può richiedere un po 'di attività ... Cerco di illustrare alcune nozioni di base sul mio blog; Anche il libro di Jon (C# in Depth) offre una panoramica di alto livello. –

risposta

15

Qualcosa di simile:

static IEnumerable<T> WhereLike<T>(
     this IEnumerable<T> data, 
     string propertyOrFieldName, 
     string value) 
{ 
    var param = Expression.Parameter(typeof(T), "x"); 
    var body = Expression.Call(
     typeof(Program).GetMethod("Like", 
      BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public), 
      Expression.PropertyOrField(param, propertyOrFieldName), 
      Expression.Constant(value, typeof(string))); 
    var lambda = Expression.Lambda<Func<T, bool>>(body, param); 
    return data.Where(lambda.Compile()); 
} 
static bool Like(string a, string b) { 
    return a.Contains(b); // just for illustration 
} 

In termini di un Func<Expression,Expression,Expression>:

static Expression Like(Expression lhs, Expression rhs) 
{ 
    return Expression.Call(
     typeof(Program).GetMethod("Like", 
      BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public) 
      ,lhs,rhs); 
} 
+0

sembra carino, ma ho bisogno di sth. che restituisce un Func Ma la domanda è, che cosa è espressione1, espressione2 ed espressione3 in questo contesto? Un esempio di come Expression.Equal funziona internamente che sarebbe bello. –

+0

Devo ammettere, non capisco tutta la magia dietro il codice, ma il secondo pice di codice funziona come un incantesimo. –

+0

@Marc Funziona con LINQ sulle entità? –

3

Ho creato 2 metodi di estensione WhereFilter() per IEnumerable e IQueryable. In questo modo è possibile utilizzare questo filtro anche con ad es. Entity Framework ed è il filtraggio eseguito sul server.

ho usato un filtro basato su * (no?) Così ho potuto utilizzare l'underlaying metodi Linq StartsWith(), EndsWith() e Contains(). Formati supportati: A *, * A * A *, A * B

Usage:

var filtered = list.WhereFilter(i => i.Name, "a*", "First Name"); 

Ecco le basi della classe:

/// <summary> 
/// Extension Methods for Filtering on IQueryable and IEnumerable 
/// </summary> 
internal static class WhereFilterExtensions 
{ 
    /// <summary> 
    /// Filters a sequence of values based on a filter with asterix characters: A*, *A, *A*, A*B 
    /// </summary> 
    /// <param name="source"></param> 
    /// <param name="selector">Field to use for filtering. (E.g: item => item.Name)</param> 
    /// <param name="filter">Filter: A*, *A, *A*, A*B</param> 
    /// <param name="fieldName">Optional description of filter field used in error messages</param> 
    /// <returns>Filtered source</returns> 
    public static IEnumerable<T> WhereFilter<T>(this IEnumerable<T> source, Func<T, string> selector, string filter, string fieldName) 
    { 

     if (filter == null) 
      return source; 

     if (selector == null) 
      return source; 

     int astrixCount = filter.Count(c => c.Equals('*')); 
     if (astrixCount > 2) 
      throw new ApplicationException(string.Format("Invalid filter used{0}. '*' can maximum occur 2 times.", fieldName == null ? "" : " for '" + fieldName + "'")); 

     if (filter.Contains("?")) 
      throw new ApplicationException(string.Format("Invalid filter used{0}. '?' is not supported, only '*' is supported.", fieldName == null ? "" : " for '" + fieldName + "'")); 


     // *XX* 
     if (astrixCount == 2 && filter.Length > 2 && filter.StartsWith("*") && filter.EndsWith("*")) 
     { 
      filter = filter.Replace("*", ""); 
      return source.Where(item => selector.Invoke(item).Contains(filter)); 
     } 

     // *XX 
     if (astrixCount == 1 && filter.Length > 1 && filter.StartsWith("*")) 
     { 
      filter = filter.Replace("*", ""); 
      return source.Where(item => selector.Invoke(item).EndsWith(filter)); 
     } 

     // XX* 
     if (astrixCount == 1 && filter.Length > 1 && filter.EndsWith("*")) 
     { 
      filter = filter.Replace("*", ""); 
      return source.Where(item => selector.Invoke(item).StartsWith(filter)); 
     } 

     // X*X 
     if (astrixCount == 1 && filter.Length > 2 && !filter.StartsWith("*") && !filter.EndsWith("*")) 
     { 
      string startsWith = filter.Substring(0, filter.IndexOf('*')); 
      string endsWith = filter.Substring(filter.IndexOf('*') + 1); 

      return source.Where(item => selector.Invoke(item).StartsWith(startsWith) && selector.Invoke(item).EndsWith(endsWith)); 
     } 

     // XX 
     if (astrixCount == 0 && filter.Length > 0) 
     { 
      return source.Where(item => selector.Invoke(item).Equals(filter)); 
     } 

     // * 
     if (astrixCount == 1 && filter.Length == 1) 
      return source; 

     // Invalid Filter 
     if (astrixCount > 0)    
      throw new ApplicationException(string.Format("Invalid filter used{0}.", fieldName == null ? "" : " for '" + fieldName + "'")); 

     // Empty string: all results 
     return source; 


    } 

    /// <summary> 
    /// Filters a sequence of values based on a filter with asterix characters: A*, *A, *A*, A*B 
    /// </summary> 
    /// <param name="source"></param> 
    /// <param name="selector">Field to use for filtering. (E.g: item => item.Name)  </param> 
    /// <param name="filter">Filter: A*, *A, *A*, A*B</param> 
    /// <param name="fieldName">Optional description of filter field used in error messages</param> 
    /// <returns>Filtered source</returns> 
    public static IQueryable<T> WhereFilter<T>(this IQueryable<T> source, Expression<Func<T, string>> selector, string filter, string fieldName) 
    { 

     if (filter == null) 
      return source; 

     if (selector == null) 
      return source; 

     int astrixCount = filter.Count(c => c.Equals('*')); 
     if (astrixCount > 2) 
      throw new ApplicationException(string.Format("Invalid filter used{0}. '*' can maximum occur 2 times.", fieldName==null?"":" for '" + fieldName + "'")); 

     if (filter.Contains("?"))    
      throw new ApplicationException(string.Format("Invalid filter used{0}. '?' is not supported, only '*' is supported.", fieldName == null ? "" : " for '" + fieldName + "'")); 

     // *XX* 
     if (astrixCount == 2 && filter.Length > 2 && filter.StartsWith("*") &&   filter.EndsWith("*")) 
     { 
      filter = filter.Replace("*", ""); 
      return source.Where(
       Expression.Lambda<Func<T, bool>>(
        Expression.Call(selector.Body, "Contains", null, Expression.Constant(filter)), 
        selector.Parameters[0] 
       ) 
      ); 
     } 

     // *XX 
     if (astrixCount == 1 && filter.Length > 1 && filter.StartsWith("*")) 
     { 
      filter = filter.Replace("*", ""); 
      return source.Where(
       Expression.Lambda<Func<T, bool>>(
        Expression.Call(selector.Body, "EndsWith", null, Expression.Constant(filter)), 
        selector.Parameters[0] 
       ) 
      ); 
     } 

     // XX* 
     if (astrixCount == 1 && filter.Length > 1 && filter.EndsWith("*")) 
     { 
      filter = filter.Replace("*", ""); 
      return source.Where(
       Expression.Lambda<Func<T, bool>>(
        Expression.Call(selector.Body, "StartsWith", null,   Expression.Constant(filter)), 
        selector.Parameters[0] 
       ) 
      ); 
     } 

     // X*X 
     if (astrixCount == 1 && filter.Length > 2 && !filter.StartsWith("*") && !filter.EndsWith("*")) 
     { 
      string startsWith = filter.Substring(0, filter.IndexOf('*')); 
      string endsWith = filter.Substring(filter.IndexOf('*') + 1); 

      return source.Where(
       Expression.Lambda<Func<T, bool>>(
        Expression.Call(selector.Body, "StartsWith", null,   Expression.Constant(startsWith)), 
        selector.Parameters[0] 
       ) 
      ).Where(
       Expression.Lambda<Func<T, bool>>(
        Expression.Call(selector.Body, "EndsWith", null,   Expression.Constant(endsWith)), 
        selector.Parameters[0] 
       ) 
      ); 
     } 

     // XX 
     if (astrixCount == 0 && filter.Length > 0) 
     { 
      return source.Where(
       Expression.Lambda<Func<T, bool>>(
        Expression.Equal(selector.Body, Expression.Constant(filter)), 
        selector.Parameters[0] 
       ) 
      ); 
     } 

     // * 
     if (astrixCount == 1 && filter.Length == 1) 
      return source; 

     // Invalid Filter 
     if (astrixCount > 0) 
      throw new ApplicationException(string.Format("Invalid filter used{0}.", fieldName == null ? "" : " for '" + fieldName + "'")); 

     // Empty string: all results 
     return source; 

    } 
} 
+0

di 'con ad es. Entity Framework ed è il filtraggio eseguito sul server "intendi nel database - ovvero: tradotto in SQL? Avevo l'impressione che 'selector.Invoke' impedisse a EF di tradurre in SQL? – JoeBrockhaus

+0

Sì, ExpressionTree viene convertito dal 'Provider LINQ'. Quando si utilizza Enity Framework, il provider LINQ to Entities lo convertirà in una stringa SQL che verrà eseguita sul database. Il risultato non sarà una matrice ma un oggetto IEnumerable che attraversa il DataReader. –