Search code examples
c#.netasp.net-coreexpressionexpression-trees

Converting LINQ expression to expression tree queryable.Where(c => c.Tags != null && searchValues.All(s => c.Tags.Contains(s)));


I have a list of strings string[] searchValues and a LINQ expression

queryable.Where(c => c.Tags != null && searchValues.All(s => c.Tags.Contains(s)));

where .Tags is a List<string>

I want to rewrite it as an expression (filters and sorting orders are coming from UI as strings, and I wanted to create a generic method to convert it to expressions)

So far I've written this, but it's incorrect.

private static Expression GetFilterExpressionIListString2(MemberExpression memberExp, string[] values)
{
    
    var anyMethod = typeof(Enumerable).GetMethods().First(m => m.Name == "Any" && m.GetParameters().Length == 2);
    var specificAnyMethod = anyMethod.MakeGenericMethod(typeof(string));
    Expression<Func<List<string>, bool>> lambda = s => values.All(v => s.Contains(v));
    var anyExp = Expression.Call(specificAnyMethod, memberExp, lambda);
    var notNullExp = Expression.NotEqual(memberExp, Expression.Constant(null));
    var andExp = Expression.AndAlso(notNullExp, anyExp);
    return andExp;
}

// an example of a dynamicly built tree that works
// .Tags.Wheere(x => x != null && values.Contains(x.Title))
private static Expression GetFilterExpressionString(MemberExpression memberExp, string[] values)
{
    MethodInfo containsMethod = typeof(ICollection<string>).GetMethod("Contains", new[] { typeof(string) });
    ConstantExpression constantExp = Expression.Constant(values);
    MethodCallExpression containsExpression = Expression.Call(constantExp, containsMethod, memberExp);
    return containsExpression;
}



Solution

  • The following function generates needed Lambda body:

    private static Expression GetFilterExpressionIListString2(MemberExpression memberExpression, string[] values)
    {
        var param          = Expression.Parameter(typeof(string), "s");
        var valuesConstant = Expression.Constant(values);
    
        var allLambda = Expression.Lambda<Func<string, bool>>(
            Expression.Call(typeof(Enumerable), nameof(Enumerable.Contains), new[] { typeof(string) },
                valuesConstant, param), param);
    
        var filterExpression =
            Expression.AndAlso(
                Expression.NotEqual(memberExpression, Expression.Constant(null, memberExpression.Type)),
                Expression.Call(typeof(Enumerable), nameof(Enumerable.All), new[] { typeof(string) }, valuesConstant, allLambda)
            );
    
        return filterExpression;
    }