How to modify type parameter of Expression>?

前端 未结 3 1261
青春惊慌失措
青春惊慌失措 2021-01-04 22:59

I have an instance of the following:

Expression>

I wish to convert it to an instance of the following

相关标签:
3条回答
  • 2021-01-04 23:30

    Fortunately, for what you want it is not necessary to play with expression trees. What you do need is to enhance the template restriction:

    public static IQueryable<TModel> FilterByDate<TModel>(this IQueryable<TModel> src, DateTime startDate, DateTime endDate) where TModel: class, IRequiredDate {
        return src.Where(x => x.Date >= startDate && x.Date <= endDate);
    }
    

    A bit of explanation. Using LINQPad you can see that the expression trees generated are different when the class requirement is removed. The Where clause is like this when the restriction is present:

    .Where (x => (x => x.Date >= startDate && x.Date <= endDate))
    

    Whereas when the restriction is removed the expression changes as follows:

    .Where (x => (x => (((IRequiredDate)x).Date >= startDate) && (((IRequiredDate)x).Date <= endDate)))
    

    The expression tree has some extra casts, which is why in this form Entity Framework tells you it cannot work with instances of type IRequiredDate.

    0 讨论(0)
  • 2021-01-04 23:46

    So the method to actually do the mapping isn't that hard, but sadly there isn't a good way that I can see of generalizing it. Here is a method that takes a Func<T1, TResult> and maps it to a delegate where the parameter is something more derived than T1:

    public static Expression<Func<NewParam, TResult>> Foo<NewParam, OldParam, TResult>(
        Expression<Func<OldParam, TResult>> expression)
        where NewParam : OldParam
    {
        var param = Expression.Parameter(typeof(NewParam));
        return Expression.Lambda<Func<NewParam, TResult>>(
            expression.Body.Replace(expression.Parameters[0], param)
            , param);
    }
    

    This uses the Replace method to replace all instances of one expression with another. The definition is:

    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);
        }
    }
    
    public static Expression Replace(this Expression expression,
        Expression searchEx, Expression replaceEx)
    {
        return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);
    }
    

    Now we can use this method (which should be given a better name) like so:

    Expression<Func<object, bool>> oldExpression = whatever;
    Expression<Func<string, bool>> newExpression =
        Foo<string, object, bool>(oldExpression);
    

    And of course since Func is actually covariant with respect to its parameters, we can be sure that any calls to this method generate expressions that won't add runtime failure points.

    You could trivially make versions of this for Func<T1, T2, TResult>, and so on and so forth up through the 16 different types of Func if you wanted, just creating a parameter expression for each, and replacing all of the old ones with new ones. It'd be tedious, but just following the pattern. Given that there needs to be a generic argument for both the old and new parameter types though, and that there's no way of inferring the arguments, that'd get...messy.

    0 讨论(0)
  • 2021-01-04 23:50

    I only had a few minutes so I haven't thought on this deeply. Does this help?

    Expression<Func<IList, bool>> exp1 = (list => list.Count > 0);
    Expression<Func<string[], bool>> exp2 = (list => exp1.Compile()(list));
    Expression<Func<List<int>, bool>> exp3 = (list => exp1.Compile()(list));
    

    I kinda demonstrates what you want I think.

    0 讨论(0)
提交回复
热议问题