Search code examples
c#linqlinq-expressions

How to pass a variable to predicate expression?


I'm creating Expression<Func<Entity, bool>> expression tree dynamically:

int id = ...;
Expression<Func<Entity, int>> keySelector = e => e.Id;
BinaryExpression equals = Expression.Equal(
   keySelector.Body, 
   Expression.Constant(id, keySelector.Body.Type)); //replace this with a variable

var predicate = Expression.Lambda<Func<Entity, bool>>(equals, keySelector.Parameters[0]);

which produces equivalent of e => e.Id == 1.

How do I pass a variable instead of a constant: e => e.Id == id


Solution

  • You need to change the const to something that you can change a property of:

    class IDHolder
    {
        public int Id { get; set; }
    }
    

    now you bind your expression to instance of IDHolder like so:

    var idHolder = new IDHolder { Id = 0 };
    Expression<Func<Entity, int>> keySelector = e => e.Id;
    
    BinaryExpression equals = Expression.Equal(
        keySelector.Body,
               
        Expression.Property(
            Expression.Constant(idHolder, typeof(IDHolder)),
        nameof(IDHolder.Id))
               
        );
    
    var predicate = Expression.Lambda<Func<Entity, bool>>(equals, keySelector.Parameters[0]);
    

    the predicate becomes slightly more complicated yet nothing that would make EF break:

    x => x.Id == idHandler.Id
    

    you can test this by compiling the predicate and running simple tests changing Id and the entity:

    var func = predicate.Compile();
    
    idHolder.Id = 5;
    
    Console.WriteLine(func(new Entity { Id = 0 }).ToString());  // false
    Console.WriteLine(func(new Entity { Id = 5 }).ToString());  // true
    
    idHolder.Id = 6;
    
    Console.WriteLine(func(new Entity { Id = 6 }).ToString());   // true
    

    note that this approach is not thread safe and will result in strange behavior if you cache predicate and idHolder and use that in web api project or anything potentially using this predicate concurrently. To just auto translate some conditions from requests it would be better to just create the expression each time you need to use it in EF. There is no significant performance hit while creating expressions; the costly operation is the compilation and that is never done by EF.


    Or maybe I misunderstood your question and you simply need a way to change the value of id, and as suggested by @Jochem Van Hespen you need a function:

    public Expression<Func<Entity,bool>> GetPredicate(int id){
    
        Expression<Func<Entity, int>> keySelector = e => e.Id;
        BinaryExpression equals = Expression.Equal(
           keySelector.Body, 
           Expression.Constant(id, keySelector.Body.Type)); 
    
        var predicate = Expression.Lambda<Func<Entity, bool>>(equals, keySelector.Parameters[0]);
        return predicate;
        }
    

    and you can use it like this:

     _dbContext.Set<Entity>().Where(GetPredicate(4))