1

To set a comparison operator in linq query dynamically, i do the following:

parameter = Expression.Parameter(typeof(SomeType));
var predicate = Expression.Lambda<Func<SomeType, bool>>(
    Combine(
        "=",
        Expression.Property(parameter, "ID"),
        Expression.Constant(150497)
    ), parameter);

BinaryExpression Combine(string op, Expression left, Expression right)
{
    switch (op)
    {
        case "=":
            return Expression.Equal(left, right);
        case "<":
            return Expression.LessThan(left, right);
        case ">":
            return Expression.GreaterThan(left, right);
    }
    return null;
}

That works. But I'd rather pass a lambda expression as parameter "left" instead. Is that possible? Something like:

var predicate = Expression.Lambda<Func<SomeType, bool>>(Combine(
    "=",
    c => c.ID,
    Expression.Constant(150497)
), parameter);
Adriaan
  • 17,741
  • 7
  • 42
  • 75
user3231784
  • 95
  • 1
  • 6

2 Answers2

1

What about this? Unfortunately, I cannot test it now, so give me know if it doesn't work

private class TestCalss
{
    public int Id { get; set; }
}

private class SwapVisitor : ExpressionVisitor
{
    public readonly Expression _from;
    public readonly Expression _to;

    public SwapVisitor(Expression from, Expression to)
    {
        _from = from;
        _to = to;
    }

    public override Expression Visit(Expression node) => node == _from ? _to : base.Visit(node);
}

private BinaryExpression Combine<T, TResult>(string op, Expression<Func<T, TResult>> left, Expression right, ParameterExpression parameter)
{
    // Need to use parameter from outer lambda expression for equality two expressions 
    var swap = new SwapVisitor(left.Parameters[0], parameter);
    var newLeft = swap.Visit(left) as Expression<Func<T, TResult>>;
    switch (op)
    {
        case "=":
            return Expression.Equal(newLeft.Body, right);
        case "<":
            return Expression.LessThan(newLeft.Body, right);
        case ">":
            return Expression.GreaterThan(newLeft.Body, right);
    }
    return null;
}

...
var parameter = Expression.Parameter(typeof(TestCalss));
var predicate = Expression.Lambda<Func<TestCalss, bool>>(
    Combine<TestCalss, int>("=", c => c.Id, Expression.Constant(156), parameter),
    parameter);
var test = new TestCalss { Id = 156 };
var result = predicate.Compile()(test); // <- true
George Alexandria
  • 2,841
  • 2
  • 16
  • 24
  • var predicate = Expression.Lambda>( Combine(...); causes the error: InvalidOperationException: The binary operator Equal is not defined for the types 'System.Func`2[SomeType,System.Int32]' and 'System.Int32'. – user3231784 Jun 24 '17 at 18:54
  • @user3231784 I fixed a problems, so you can see my edited answer. I wrote small test class for simplicity and understanding – George Alexandria Jun 24 '17 at 22:31
  • Smart answer and very helpful for me. Thanks! – user3231784 Jun 25 '17 at 15:51
0

Basically you want to access a class' fields without using strings, and that is doable iff your fields are public.

Here you can see a good example of how that's done.


As for your specific usage of it, it'd be something along the lines of:

public class Test
{

    public static void someMethod()
    {
        var parameter = Expression.Parameter(typeof(SomeType));
        var predicate = Expression.Lambda<Func<SomeType, bool>>(Combine(
                            "=",
                            Expression.Parameter(typeof(int), GetMemberName((SomeType c) => c.ID)),
                            Expression.Constant(150497)
                        ), parameter);
    }

    public static BinaryExpression Combine(string op, Expression left, Expression right)
    {
        switch (op)
        {
            case "=":
                return Expression.Equal(left, right);
            case "<":
                return Expression.LessThan(left, right);
            case ">":
                return Expression.GreaterThan(left, right);
        }
        return null;
    }

    public static string GetMemberName<T, TValue>(Expression<Func<T, TValue>> memberAccess)
    {
        return ((MemberExpression)memberAccess.Body).Member.Name;
    }

}

public class SomeType
{
    public int ID { get; set; }
    private string aString;
}

Disclaimer: Didn't test it, but logic is there.

As mentionned, you wouldn't be able to access SomeType.aString because it is private. Also I put typeof(int) but if you want even that to be dynamic you could have another method (i.e. GetMemberType) to get the field's type.

Mat
  • 1,440
  • 1
  • 18
  • 38
  • That works, but to have no strings involved is only one reason i want to use lambda expressions, another is to access also navigation properties, e.g. `c => c.SomeProperty.Value == 5` – user3231784 Jun 24 '17 at 19:28
  • Not quite sure what you mean but you should add any necessary info to the original question since people can't guess your problem. That being said, if you have a property `public int ID { get { return id; } set { id = value; } }` that refers to a field `private int id;`, that solution still works. – Mat Jun 24 '17 at 19:34