简体   繁体   中英

Converting selector expression to predicate expression

I need a way to convert a selector expression into a predicate expression in order to pass it to a query provider along with some parameters.

private Expression<Func<T, TKey>> PrimaryKeySelector { get; set; }

private ISpecification<T> GetByPrimaryKeySpecification(TKey key) {
   lambda = ... // some magic, also unicorns
   return new ExpressionSpecification(lambda);
}

In a first try I built the predicate expression using Expression.Equal , however this caused problems with query providers that analyze the expression (eg NHibernate failed because of unknown Equal statement). Also, I've seen solutions that create new ParameterExpression s and use the result of the selector expression by invoking it, however I feel this is just bad because 1) performance and 2) the parameter expression is already included in the selector expression .

Any suggestions?

What you're looking to do here is to compose one expression with another. Doing this with functions is very easy; doing it with expressions takes a bit more work.

What you can do is replace all instances of the parameter to your second expression, the predicate, with the body of your first function, the selector.

public static Expression<Func<TFirstParam, TResult>>
    Compose<TFirstParam, TIntermediate, TResult>(
    this Expression<Func<TFirstParam, TIntermediate>> first,
    Expression<Func<TIntermediate, TResult>> second)
{
    var param = Expression.Parameter(typeof(TFirstParam), "param");

    var newFirst = first.Body.Replace(first.Parameters[0], param);
    var newSecond = second.Body.Replace(second.Parameters[0], newFirst);

    return Expression.Lambda<Func<TFirstParam, TResult>>(newSecond, param);
}

This relies on the following method to replace all instances of one expression with another:

public static Expression Replace(this Expression expression,
    Expression searchEx, Expression replaceEx)
{
    return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);
}
internal class ReplaceVisitor : ExpressionVisitor
{
    private readonly Expression from, to;
    public ReplaceVisitor(Expression from, Expression to)
    {
        this.from = from;
        this.to = to;
    }
    public override Expression Visit(Expression node)
    {
        return node == from ? to : base.Visit(node);
    }
}

The final missing puzzle piece is that you won't be able to use the == operator to compare your values in a generic method as you won't be able to be sure that the type overlads the == operator; you'll need to build that equality expression manually:

public static Expression<Func<T, bool>> EqualsValue<T>(T value)
{
    var param = Expression.Parameter(typeof(T));
    var body = Expression.Equal(param, Expression.Constant(value));
    return Expression.Lambda<Func<T, bool>>(body, param);
}

This allows you to write:

return PrimaryKeySelector.Compose(EqualsValue(key));

As to your concerns about performance, don't worry about it. The amount of time that you'll be spending building up an expression tree like this is going to be many orders of magnitude slower than the network requests that you'll be making with them. Spending a little extra time on the expression manipulation isn't even going to have a measurable effect on the performance of your application; it will be well below the noise of the network request.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM