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);

2 Answers 2

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
Sign up to request clarification or add additional context in comments.

3 Comments

var predicate = Expression.Lambda<Func<SomeType, bool>>( Combine<SomeType, int>(...); causes the error: InvalidOperationException: The binary operator Equal is not defined for the types 'System.Func`2[SomeType,System.Int32]' and 'System.Int32'.
@user3231784 I fixed a problems, so you can see my edited answer. I wrote small test class for simplicity and understanding
Smart answer and very helpful for me. Thanks!
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.

2 Comments

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
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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.