0

I'm writing a query framework, and trying to make it as generic as possible.

Let's say I have a query based on person, and I want the ability to filter on both the first and last names, and in both cases I want to be able to use filter conditions like StartsWith, 'EndsWith, Contains, Equals.

So now I have a method:

private Expression<Func<Person, bool>> FirstNameFilter(Comparator comparator, string compareValue) {
  switch (comparator) {
    case Comparator.Equal:
      return p => p.FirstName == compareValue;
    case Comparator.Contains:
      return p => p.FirstName.Contains(compareValue);
    case Comparator.StartsWith:
      return p => p.FirstName.StartsWith(compareValue);
    // etc.
  }
}

Now, I also want to be able to build the same filter for LastName. Seems silly and wasteful to copy and paste the whole thing over again, just replacing p.FirstName with p.LastName. I also have a bunch of other string fields that I want to filter on, and I really don't want to have to rewrite this whole method for each one!

Is there some way to abstract this, maybe using LinqKit, so that I can come out with a more generic method with the following approximate signature:

Expression<Func<Person, bool>> GetFilter(Expression<Func<Person, string>> stringExpression, Comparator comparator, string compareValue) {}

such that I could, inside FirstNameFilter, invoke it like so:

return GetFilter(p => p.FirstName, comparator, compareValue);
Shaul Behr
  • 36,951
  • 69
  • 249
  • 387
  • You will need to play with the expression tree to do that. There's a good SO post for a simple `==` comparison [here](http://stackoverflow.com/questions/8315819/expression-lambda-and-query-generation-at-runtime-simplest-where-example). Or if you want to use LinqKit, see [Dynamic Queries #1: Selecting Customers](http://tomasp.net/blog/dynamic-linq-queries.aspx). – Simon Belanger Jul 04 '13 at 18:35

1 Answers1

3

Something like that (untested, but you have the idea) should help you to build the needed expressions :

public static class LinqQueries
{
    private static MethodInfo toLowerMethod = typeof(String).GetMethod("ToLower", Type.EmptyTypes);
    private static MethodInfo startsWithMethod= typeof(String).GetMethod("StartsWith", new Type[] { typeof(String) });
    private static MethodInfo containsMethod = typeof(String).GetMethod("Contains", new Type[] { typeof(String) });
    private static MethodInfo endsWithMethod= typeof(String).GetMethod("EndsWith", new Type[] { typeof(String) });

    public static Expression<Func<T, bool>> GetFilter(Expression<Func<T, string>> expression, Comparator comparator, string compareValue) {
        ParameterExpression parameterExpression = null;
        var memberExpression = GetMemberExpression(expression.Body, out parameterExpression);
        Expression constExp = Expression.Constant(compareValue);
        switch (comparator) {

        case Comparator.Contains:
          memberExpression = Expression.Call(memberExpression, containsMethod,constExp);
        break;
        case Comparator.StartsWith:
          memberExpression = Expression.Call(memberExpression, startsWithMethod, constExp);
        break;
        //etc.
        default :
          memberExpression = Expression.Equal(memberExpression, constExp);
        break;
      }

      return Expression.Lambda<Func<T, bool>>(memberExpression, new[]{parameterExpression});
  }


  private static Expression GetMemberExpression(Expression expression, out ParameterExpression parameterExpression)
    {
        parameterExpression = null;
        if (expression is MemberExpression)
        {
            var memberExpression = expression as MemberExpression;
            while (!(memberExpression.Expression is ParameterExpression))
                memberExpression = memberExpression.Expression as MemberExpression;
            parameterExpression = memberExpression.Expression as ParameterExpression;
            return expression as MemberExpression;
        }
        if (expression is MethodCallExpression)
        {
            var methodCallExpression = expression as MethodCallExpression;
            parameterExpression = methodCallExpression.Object as ParameterExpression;
            return methodCallExpression;
        }
        return null;
    }
}
Raphaël Althaus
  • 59,727
  • 6
  • 96
  • 122
  • why not just use `expression.Body` as an argument for Expression.Call/Expression.Equal and `expression.parameters` as resulting lambda expression parameters? – Konstantin Oznobihin Jul 08 '13 at 15:12
  • BTW, I'm struggling a little with implementation. If I have a method call expression, I'm getting back a null `parameterExpression`, which then throws an Exception in Expression.Lambda. Besides, Expression.Lambda is expecting an `IEnumerable`. I tweaked the code a little, but I'm still not having any success. Help please? – Shaul Behr Jul 09 '13 at 21:37
  • @Shaul First, Expression.Lambda takes an `IEnumerable`, that's why we pass it a `new[]{parameterExpression}` (so this part should be ok). Then, could you give some sample code ? `GetMemberExpression` returns null, but with which method call Expression ? – Raphaël Althaus Jul 10 '13 at 07:56
  • Discussion continues in [chat](http://chat.meta.stackoverflow.com/rooms/609/http-stackoverflow-com-q-17475986-7850)... see you there... :) – Shaul Behr Jul 10 '13 at 08:05