简体   繁体   English

如何修改Expression的类型参数 <Func<???, bool> &gt;?

[英]How to modify type parameter of Expression<Func<???, bool>>?

I have an instance of the following: 我有以下实例:

Expression<Func<IRequiredDate, bool>>

I wish to convert it to an instance of the following, so it can be used to run a query in Entity Framework: 我希望将其转换为以下实例,因此它可用于在Entity Framework中运行查询:

Expression<Func<TModel, bool>>

This will allow me to utilize a generic filtering query to any Model which implements IRequiredDate, eg.: 这将允许我对任何实现IRequiredDate的Model使用通用过滤查询,例如:

// In some repository function:
var query = DbContext.Set<Order>()
     .FilterByDateRange(DateTime.Today, DateTime.Today);

var query = DbContext.Set<Note>()
     .FilterByDateRange(DateTime.Today, DateTime.Today);

var query = DbContext.Set<Complaint>()
     .FilterByDateRange(DateTime.Today, DateTime.Today);


// The general purpose function, can filter for any model implementing IRequiredDate
public static IQueryable<TModel> FilterByDate<TModel>(IQueryable<TModel> query, DateTime startDate, DateTime endDate) where TModel : IRequiredDate
{
    // This will NOT WORK, as E/F won't accept an expression of type IRequiredDate, even though TModel implements IRequiredDate
    // Expression<Func<IRequiredDate, bool>> dateRangeFilter = x => x.Date >= startDate && x.Date <= endDate;
    // query = query.Where(dateRangeFilter);

    // This also WON'T WORK, x.Date is compiled into the expression as a member of IRequiredDate instead of TModel, so E/F knocks it back for the same reason:
    // Expression<Func<TModel, bool>> dateRangeFilter = x => x.Date >= startDate && x.Date <= endDate;
    // query = query.Where(dateRangeFilter);

    // All you need is lov.... uh... something like this:
    Expression<Func<IRequiredDate, bool>> dateRangeFilter = x => x.Date >= startDate && x.Date <= endDate;
    Expression<Func<TModel, bool>> dateRangeFilterForType = ConvertExpressionType<IRequiredDate, TModel>(dateRangeFilter); // Must convert the expression from one type to another
    query = query.Where(dateRangeFilterForType) // Ahhhh. this will work.

    return query;
}

public static ConvertExpressionType<TInterface, TModel>(Expression<Func<TInterface, bool>> expression)
where TModel : TInterface // It must implement the interface, since we're about to translate them
{
    Expression<Func<TModel, bool>> newExpression = null;

    // TODO: How to convert the contents of expression into newExpression, modifying the
    // generic type parameter along the way??

    return newExpression;
}

I understand that they are different types and cannot be cast. 我知道他们是不同的类型,不能演员。 However I am wondering if there is a way to create a new Expression<Func<TModel, bool>> , then rebuild it based on the contents of the Expression<Func<IRequiredDate, bool>> provided, switching any type references from IRequiredDate to TModel in the process. 但是我想知道是否有办法创建一个新的Expression<Func<TModel, bool>> ,然后根据提供的Expression<Func<IRequiredDate, bool>>的内容重建它,将任何类型引用从IRequiredDate切换到TModel在这个过程中。

Can this be done? 可以这样做吗?

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 : 这是一个采用Func<T1, TResult>并将其映射到委托的方法,其中参数是比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. 这使用Replace方法将一个表达式的所有实例替换为另一个表达式。 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. 当然,由于Func实际上是关于其参数的协变,我们可以确定对此方法的任何调用都会生成不会添加运行时故障点的表达式。

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. 如果你愿意的话,你可以通过16种不同类型的Func轻松地为Func<T1, T2, TResult>制作这个版本,只需为每个类型创建一个参数表达式,并替换所有旧的`新的。 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. 鉴于旧的和新的参数类型都需要一个泛型参数,并且没有办法推断出参数,那就太麻烦了。

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. 使用LINQPad,您可以看到在删除class要求时生成的表达式树是不同的。 The Where clause is like this when the restriction is present: 当限制存在时, Where子句是这样的:

.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 . 表达式树有一些额外的强制转换,这就是为什么在这种形式下,实体框架告诉你它无法使用IRequiredDate类型的IRequiredDate

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. 我有点展示你想要的东西。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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