如何修改表达式<Func<???, bool>>的类型参数?



我有一个以下的实例:

Expression<Func<IRequiredDate, bool>>

我希望将其转换为以下实例,以便可以使用它在实体框架中运行查询:

Expression<Func<TModel, bool>>

这将允许我对任何实现IRequiredDate的模型使用通用过滤查询,例如:

// 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;
}

我知道它们是不同类型的,不能强制转换。然而,我想知道是否有一种方法可以创建一个新的Expression<Func<TModel, bool>>,然后根据提供的Expression<Func<IRequiredDate, bool>>的内容重新构建它,在这个过程中将任何类型引用从IRequiredDate切换到TModel

这能做到吗?

因此,实际进行映射的方法并不难,但遗憾的是,我看不到推广它的好方法。这里有一个方法,它使用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);
}

这使用Replace方法将一个表达式的所有实例替换为另一个表达式。定义是:

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);
}

现在我们可以这样使用这种方法(应该给它一个更好的名称):

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

当然,由于Func实际上相对于其参数是协变的,我们可以确信,对该方法的任何调用都会生成不会添加运行时故障点的表达式。

如果需要的话,您可以为Func<T1, T2, TResult>制作这样的版本,以此类推,直到16种不同类型的Func,只需为每种类型创建一个参数表达式,并用新表达式替换所有旧表达式。这将是乏味的,但只是遵循模式。然而,考虑到新旧参数类型都需要一个通用参数,而且无法推断参数,那么。。。凌乱的

幸运的是,对于您想要的内容,没有必要使用表达式树。您需要的是增强模板限制:

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);
}

一点解释。使用LINQPad可以看到,当class需求被删除时,生成的表达式树是不同的。当存在限制时,Where子句是这样的:

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

然而,当限制被去除时,表达式的变化如下:

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

表达式树有一些额外的强制类型转换,这就是为什么在这种形式中,EntityFramework告诉它不能使用IRequiredDate类型的实例。

我只有几分钟的时间,所以我还没有深入思考。这有帮助吗?

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));

我想我有点展示了你想要什么。

最新更新