How to NULL check and do a ToLower call along with Contains call dynamically using Expression Tree in C#

63 Views Asked by At

I'm trying to dynamically generate the following expression

x => x?.ToLower().Contains(value?.ToLower())

on my Item class.

Here is my Item class:

public class Item
{
    public string? Type { get; set; }
}

I was able to do a NULL check and Contains call using the following code.

var param = Expression.Parameter(typeof(Item), "i");
MethodInfo containsMethod = typeof(string).GetMethod("Contains", new[] { typeof(string) });

var itemTypeMember = Expression.Property(param, "Type");
var itemTypeNullCheck = Expression.Equal(itemTypeMember, Expression.Constant(null));
var itemTypeConstant = Expression.Constant(itemType, typeof(string));
var itemTypeBody = Expression.Call(itemTypeMember, containsMethod, itemTypeConstant);
var itemTypeCondition = Expression.Condition(itemTypeNullCheck, Expression.Constant(false), itemTypeBody);

var lambda = Expression.Lambda<Func<Item, bool>>(itemTypeCondition, param);

Query.Where(lambda);

So far so good. This works as expected. And I achieved till

x => x?.Contains(value)

But the problem with this is that it is not case insensitive comparison.

Now when I try to add ToLower() with above code,

MethodInfo toLowerMethod = typeof(string).GetMethod("ToLower", new[] { typeof(string) });

var itemTypeBody = Expression.Call(Expression.Call(itemTypeMember, toLowerMethod), containsMethod, itemTypeConstant);

I get the following error.

Value cannot be null. (Parameter 'method')
System.ArgumentNullException: Value cannot be null. (Parameter 'method')
   at System.ArgumentNullException.Throw(String paramName)
   at System.Linq.Expressions.Expression.Call(Expression instance, MethodInfo method)

Please assist me on what I'm missing.

2

There are 2 best solutions below

1
Luaan On BEST ANSWER

There's no ToLower method on string that accepts a string argument. this doesn't count:

typeof(string).GetMethod("ToLower", Array.Empty<Type>())
3
Svyatoslav Danyliv On

There is no need to play with Expression methods directly. You can let compiler to do a lot of work for you.

What you are tring to generates is just:

Expression<Func<Item, bool>> lambda = i => i.Type != null && i.Type.ToLower().Contains(v);

For sure it is just for playing with Expressions. If you want to do it in real life, you can write the following code. Im using EF Core's ReplacingExpressionVisitor, it's implementation is simple and can be copy pasted from EF Core code.

public static class QueryableExtensions
{
    public static Expression<Func<TItem, bool>> GenerateContains<TItem>(Expression<Func<TItem, string?>> stringProperty, string value)
    {
        Expression<Func<string?, string, bool>> template = (str, v) => str != null && str.ToLower().Contains(v);

        // replace template's parameters with stringProperty.Body and value constant
        var visitor = new ReplacingExpressionVisitor(template.Parameters, new []{ stringProperty.Body, Expression.Constant(value.ToLower())});

        var newBody = visitor.Visit(template.Body);

        var lambda = Expression.Lambda<Func<TItem, bool>>(newBody, stringProperty.Parameters);
        return lambda;
    }

    public static IQueryable<TItem> WhereContains<TItem>(this IQueryable<TItem> query, Expression<Func<TItem, string?>> stringProperty, string value)
    {
        var lambda = GenerateContains(stringProperty, value);
        return query.Where(lambda);
    }
}

And usage:

query = query.WhereContains(i => i.Type, "some");