Search code examples
c#linq-to-entitiesexpression

Build Lambda Expressions with Contains


I have a problem converting simple linq query to Lambda Expression.

My queries look like this:

int[] array = List<int> array2 = sql.OfType<Table1>().Select(x=>x.ID).Take(10).ToList();
var result = sql.OfType<Table1>().Where(x => array.Contains(x.ID)).Take(10).ToList();

and the final result should be:

static void DynamicSQLQuery<T>(IQueryable<T> sql, string fieldName)
{
    List<int> array = sql.OfType<T>().Select(SelectExpression<T>(fieldName)).Take(10).ToList();
    var result = sql.OfType<T>().Where(InExpression<T>(fieldName, array)).Take(10).ToList();
}

Class

public class Table1
{
    public int ID { get; set; }
    public string Name { get; set; }
}

I already converted the first lambda:

public static Expression<Func<T, int>> SelectExpression<T>(string fieldName)
{
    ParameterExpression param = Expression.Parameter(typeof(T), "x");
    MemberExpression selection = Expression.PropertyOrField(param, fieldName);
    var lambdaExp = Expression.Lambda<Func<T, int>>(selection, param);
    return lambdaExp;
}

But stuck on the second one:

static Expression<Func<T, bool>> InExpression<T>(string propertyName,IEnumerable<int> array)
{
    System.Reflection.MethodInfo containsMethod = typeof(IEnumerable<int>).GetMethod("Contains");
    ParameterExpression param = Expression.Parameter(typeof(T), "x");
    MemberExpression member = Expression.PropertyOrField(param, propertyName);//x.{property}
    var constant = Expression.Constant(3);
    var body = Expression.GreaterThanOrEqual(member, constant); //x.{property} >= 3 but I need  array.Contains(x.{property})
    var finalExpression = Expression.Lambda<Func<T, bool>>(body, param);
    return finalExpression;
}

Can anyone help me to make the lambda expression x=>array2.Contains(x.ID) in InExpression method?

Also, I'll be very grateful for some link to article/tutorial about creating these type of expressions.


Solution

  • Probably something like:

    static Expression<Func<T, bool>> InExpression<T>(
        string propertyName, IEnumerable<int> array)
    {
        var p = Expression.Parameter(typeof(T), "x");
        var contains = typeof(Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public)
            .Single(x => x.Name == "Contains" && x.GetParameters().Length == 2)
            .MakeGenericMethod(typeof(int));
        var property = Expression.PropertyOrField(p, propertyName);
        var body = Expression.Call(contains, Expression.Constant(array), property);
        return Expression.Lambda<Func<T, bool>>(body, p);
    }
    

    The trick here is to start off with something simple that compiles; for example:

    using System.Linq;
    using System;
    using System.Linq.Expressions;
    using System.Collections.Generic;
    
    public class C {
        static Expression<Func<Foo, bool>> InExpression<T>(
            string propertyName,IEnumerable<int> array)
        {
            return x => array.Contains(x.Id);
        }
    }
    
    class Foo {
        public int Id {get;set;}   
    }
    

    Now either compile it and look in ildasm/reflector, or (and much simpler): run that through https://sharplab.io specifying C# as the output, like this

    This shows you the code that the compiler generated:

    private static Expression<Func<Foo, bool>> InExpression<T>(string propertyName, IEnumerable<int> array)
    {
        C.<>c__DisplayClass0_0<T> <>c__DisplayClass0_ = new C.<>c__DisplayClass0_0<T>();
        <>c__DisplayClass0_.array = array;
        ParameterExpression parameterExpression = Expression.Parameter(typeof(Foo), "x");
        Expression arg_77_0 = null;
        MethodInfo arg_77_1 = methodof(IEnumerable<!!0>.Contains(!!0));
        Expression[] expr_38 = new Expression[2];
        expr_38[0] = Expression.Field(Expression.Constant(<>c__DisplayClass0_, typeof(C.<>c__DisplayClass0_0<T>)), fieldof(C.<>c__DisplayClass0_0<T>.array));
        Expression[] expr_5F = expr_38;
        expr_5F[1] = Expression.Property(parameterExpression, methodof(Foo.get_Id()));
        Expression arg_86_0 = Expression.Call(arg_77_0, arg_77_1, expr_5F);
        ParameterExpression[] expr_82 = new ParameterExpression[1];
        expr_82[0] = parameterExpression;
        return Expression.Lambda<Func<Foo, bool>>(arg_86_0, expr_82);
    }
    

    Note that there's a few things in here we need to fixup, but it allows us to see what it is doing - things like memberof and fieldof don't actually exist, for example, so we need to look them up via reflection.


    A humanized version of the above:

    private static Expression<Func<Foo, bool>> InExpression<T>(string propertyName, IEnumerable<int> array)
    {
        ExpressionState state = new ExpressionState();
        state.array = array;
        ParameterExpression parameterExpression = Expression.Parameter(typeof(Foo), "x");
        MethodInfo contains = typeof(Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public)
            .Single(x => x.Name == nameof(Enumerable.Contains) && x.GetParameters().Length == 2)
            .MakeGenericMethod(typeof(int));
        Expression[] callArgs = new Expression[2];
        callArgs[0] = Expression.Field(Expression.Constant(state, typeof(ExpressionState)), nameof(ExpressionState.array));
        callArgs[1] = Expression.Property(parameterExpression, propertyName);
        Expression body = Expression.Call(null, contains, callArgs);
        ParameterExpression[] parameters = new ParameterExpression[1];
        parameters[0] = parameterExpression;
        return Expression.Lambda<Func<Foo, bool>>(body, parameters);
    }
    

    with:

    class ExpressionState
    {
        public IEnumerable<int> array;
    }