Search code examples
c#lambdaexpression-trees

ExpressionTree to Negate a String Operation


I have a generic method that adds a lambda to an IQueryable. The one below adds a StartsWith lambda. What I'm trying to do now is create a NotStartsWith lambda. Since NotStartsWith is not a string operator, I'm not sure where to start.

I'm thinking there is (should be) a way to build an EpressionTree that handles a negate operation. But it looks like they all work with current method calls on the Type, so my next thought was to do an IndexOf and test for != 0.

Since I'm sure I'll need the solution to handle other operators, I'm hoping to find a way to create a lambda where I can make a method call and compare that result to another constant, i.e. (string).Substring(3,7) == "abcd". I just can't come up with a way to do the comparison in the ExpressionTree.

This is what I have for StartsWith and it works fine. I just need a way to negate it or (even better for future operations) find a way to build the expression with a comparison.

    static IQueryable<T> ETForStartsWith<T>(IQueryable<T> query, string propertyValue, PropertyInfo propertyInfo)
    {
        ParameterExpression e = Expression.Parameter(typeof(T), "e");
        MemberExpression m = Expression.MakeMemberAccess(e, propertyInfo);
        ConstantExpression c = Expression.Constant(propertyValue, typeof(string));
        MethodInfo mi = typeof(string).GetMethod("StartsWith", new Type[] { typeof(string) });
        Expression call = Expression.Call(m, mi, c);

        Expression<Func<T, bool>> lambda = Expression.Lambda<Func<T, bool>>(call, e);
        return query.Where(lambda);
    }

Thanks for any help!


Solution

  • Just negate a method call result:

        /// <summary>
        /// Adds "Where(_ => !_.StringProperty.StartsWith("someValue"))" to the query.
        /// </summary>
        static IQueryable<T> AddNotStartsWith<T>(IQueryable<T> query, PropertyInfo propertyInfo, string value)
        {
            var parameter = Expression.Parameter(typeof(T));
            var memberAccess = Expression.MakeMemberAccess(parameter, propertyInfo);
            var startsWithMethod = typeof(string).GetMethod("StartsWith", new[] { typeof(string) });
            var methodCall = Expression.Call(memberAccess, startsWithMethod, Expression.Constant(value));
    
            return query
                .Where(Expression.Lambda<Func<T, bool>>(Expression.Not(methodCall), parameter));
        }
    

    or in more generic fashion:

        static IQueryable<T> AddComparisonExpression<T>(IQueryable<T> query, PropertyInfo propertyInfo, Func<Expression, Expression> getComparisonExpression)
        {
            var parameter = Expression.Parameter(typeof(T));
            var memberAccess = Expression.MakeMemberAccess(parameter, propertyInfo);
    
            return query
                .Where(Expression.Lambda<Func<T, bool>>(getComparisonExpression(memberAccess), parameter));
        }
    
        /// <summary>
        /// Adds "Where(_ => !_.StringProperty.StartsWith("someValue"))" to the query.
        /// </summary>
        static IQueryable<T> AddNotStartsWith<T>(IQueryable<T> query, PropertyInfo propertyInfo, string value)
        {
            return AddComparisonExpression(query, propertyInfo, memberAccess =>
            {
                var startsWithMethod = typeof(string).GetMethod("StartsWith", new[] { typeof(string) });
                var methodCall = Expression.Call(memberAccess, startsWithMethod, Expression.Constant(value));
    
                return Expression.Not(methodCall);
            });
        }
    
        /// <summary>
        /// Adds "Where(_ => _.StringProperty.Substring(startIndex, length) == value)" to the query.
        /// </summary>
        static IQueryable<T> AddSubstringEquals<T>(IQueryable<T> query, PropertyInfo propertyInfo, int startIndex, int length, string value)
        {
            return AddComparisonExpression(query, propertyInfo, memberAccess =>
            {
                var substringMethod = typeof(string).GetMethod("Substring", new[] { typeof(int), typeof(int) });
                var methodCall = Expression.Call(memberAccess, substringMethod, Expression.Constant(startIndex), Expression.Constant(length));
    
                return Expression.Equal(methodCall, Expression.Constant(value));
            });
        }