Replace a type in an expression tree

2019-04-02 00:37发布

问题:

I'm struggling with the modification of an expression tree. I've simplified the example to make it easier to display here. Let's start with two classes:

    public class Filter
    {
        public string FilterableProperty1 { get; set; }
        public string FilterableProperty2 { get; set; }
    }

    public class Entity
    {
        public string FilterableProperty1 { get; set; }
        public string FilterableProperty2 { get; set; }
        public string NonFilterableProperty { get; set; }
    }

All properties in the Filter class also exist in the Entity class. Now I would like to use the Filter class to return the desired entities with a method like this:

    public IEnumerable<Entity> GetEntities(Expression<Func<Filter, bool>> filter)
    {
        Expression<Func<Entity, bool>> convertedFilter = Expression.Lambda<Func<Entity, bool>>(
            filter.Body,
            Expression.Parameter(typeof(Entity), filter.Parameters[0].Name));

        using (MyEntities entities = new MyEntities())
        {
            return entities.Entities.Where(convertedFilter);
        }
    }

So basically I just change the type of the expression parameter. Now when I call the function like this:

    public IEnumerable<Entity> GetFilteredEntities()
    {
        return GetEntities(x => x.FilterableProperty1 == "Test");
    }

I get an exception saying that the parameter x was not found in the specified query expression. Obviously by replacing the ParameterExpression I am breaking something. How can I create a new expression with the correct type that takes over (or rebuilds) the body of the original expression ?

回答1:

After some more searching I found the answer here: How to change a type in an expression tree?. It didn't show up in the suggestions when I submitted my question.

As I don't really need the Filter class, I instead created an interface with only the properties that I want to be able to filter (IEntity) and modified the Entity class to implement it. Now I'm able to get the desired results with this:

// Example use: return entities.Entities.Where(ExpressionTransformer<IEntity,Entity>.Transform(filter));
internal static class ExpressionTransformer<TFrom, TTo> where TTo : TFrom
{
    public class Visitor : ExpressionVisitor
    {
        private ParameterExpression _parameter;

        public Visitor(ParameterExpression parameter)
        {
            _parameter = parameter;
        }

        protected override Expression VisitParameter(ParameterExpression node)
        {
            return _parameter;
        }
    }

    public static Expression<Func<TTo, bool>> Tranform(Expression<Func<TFrom, bool>> expression)
    {
        ParameterExpression parameter = Expression.Parameter(typeof(TTo));
        Expression body = new Visitor(parameter).Visit(expression.Body);
        return Expression.Lambda<Func<TTo, bool>>(body, parameter);
    }
}

In case you need to do something similar but you cannot work with an interface or you cannot make your class implement that interface: the answer is also in the above mentioned answer