Search code examples
c#.netlinqlambdaexpression-trees

Why does passing an expression object to where returns a different result than typing the lambda expression?


I have the following code which returns a list of six objects correctly

var items = db.items.take(100);
 var result = items.Where(m => m.Cost.ToString().ToLower().Contains("67.5")).ToList(); //returns 6 items

I'm trying to do the same thing using dynamic expressions.

// Print out the expression.
// .ToString() returns "m => m.Cost.ToString().ToLower().Contains("67.5")"
var whereClause = ContainsPredicate<item>("Cost", "67.5"); 
var result = items.Where(whereClause).ToList(); //returns 0 items

When I tries to use sql profiler to see what was being sent to the DB I noticed that it removes my clause and adds WHERE 0 = 1

ContainsPredicate method implementation :

public static Expression<Func<T, bool>> ContainsPredicate<T>(string memberName, string searchValue)
        {
            var parameter = Expression.Parameter(typeof(T), "m");
            var member = Expression.PropertyOrField(parameter, memberName);

            MethodCallExpression memberToString = Expression.Call(Expression.Constant(member), member.GetType().GetMethod("ToString", Type.EmptyTypes));

            MethodCallExpression memberToLower = Expression.Call(memberToString,"ToLower", null);

            var body = Expression.Call(memberToLower,"Contains",Type.EmptyTypes,Expression.Constant(searchValue));

            return Expression.Lambda<Func<T, bool>>(body, parameter);
        }

Any advice is appreciated. Thanks.


Solution

  • public static void Test()
    {
        var myItem = new Item() { Cost = 67.5 };
        var items = new List<Item> { myItem };
    
        var result = items.Where(m => 
        m.Cost.ToString().ToLower().
        Contains("67,5")).ToList();
    
        var whereClause = ContainsPredicate<Item>("Cost", "67,5");
    
        // var test1 = whereClause(myItem);
        var result2 = items.Where(whereClause).ToList(); // returns 1 result in my case
    }
    
    public static Func<T, bool> ContainsPredicate<T>(string memberName, string searchValue)
    {
        var parameter = Expression.Parameter(typeof(T), "m");
        var member = Expression.PropertyOrField(parameter, memberName);
    
        // Mistake was here:
        var doubleToStr = member.Type.GetMethod("ToString", Type.EmptyTypes);
        MethodCallExpression memberToString = Expression.Call(member, doubleToStr);
    
        MethodCallExpression memberToLower = 
            Expression.Call(memberToString, "ToLower", null);
    
        var body = Expression.Call(memberToLower, "Contains", Type.EmptyTypes
            , Expression.Constant(searchValue));
    
        var lamb = Expression.Lambda<Func<T, bool>>(body, parameter);
    
        // we need to compile
        return lamb.Compile();
    }
    

    First thing i did was only compile memberToLower and that returned the string "m.cost" instead of your double. "m.cost" will obviously never contain "67.5". So there you go.

    Are you sure you don't want something like this:

    public static Func<T, bool> ContainsPredicate2<T>(string memberName, string searchValue)
    {
        var prop = typeof(T).GetProperty(memberName);
    
        Func<T, bool> func = (T obj2) =>
            prop.GetValue(obj2).ToString().ToLower().Contains(searchValue);
    
        return func;
    }