Search code examples
c#linqentity-framework-5expression-treeslinqkit

Compound expression with variable comparator?


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

Solution

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