我有一组报告,我需要在返回输出之前执行过滤。我想用一个匿名方法执行此操作,以避免在不同的存储库中复制相同的代码。我使用实体框架,因此模型类型都与数据库相关,并从名为ReportBase
的基类继承。
这就是我当前实现过滤的方式,每种报告类型都必须使用不同的上下文实现此方法并返回不同的IQueryable类型。
private IQueryable<ReviewAgreement> GetFiltered(ReportFilter filter)
{
IQueryable<ReviewAgreement> reviewAgreementQueryable = Context.ReviewAgreements.Where(p => p.ClientWorkflowId == filter.ClientWorkflowId);
if (filter.AppraisalLevelId.HasValue)
{
reviewAgreementQueryable = reviewAgreementQueryable.Where(p => p.AppraisalLevelId == filter.AppraisalLevelId.Value);
}
return reviewAgreementQueryable;
}
我一直试图匿名实现这个,所以我可以重复使用它,就像在这个非功能性的例子中一样。
public IQueryable<T> GetFiltered(ReportFilter filter)
{
IQueryable<T> reportQueryable = Context.Set<T>();
reportQueryable = reportQueryable.Where(p => p.ClientWorkflowId == filter.ClientWorkflowId);
if (filter.AppraisalLevelId.HasValue)
{
reportQueryable = reportQueryable.Where(p => p.AppraisalLevelId == filter.AppraisalLevelId.Value);
}
return reportQueryable;
}
我遇到的问题当然是Where
的使用含糊不清,因此无法解决p.ClientWorkflowId
。
我尝试使用Func<T, TResult>
委托来传递过滤选项,但Where操作似乎想要返回一个列表。
实际上是否有一种方法可以用来实现我想要的效果?
答案 0 :(得分:5)
请注意,如果您的基类定义了两个有问题的属性,那么您不需要接口,并且可以简单地将类型约束到该基类:
public IQueryable<T> GetFiltered<T>(ReportFilter filter) where T : ReportBase
{
// body unchanged
}
如果你想沿着接受参数的路线去表示这些属性,那么它也是可能的。首先,您需要接受表达式,而不是Func
个对象,以便查询提供程序可以分析它们。这意味着将函数签名更改为:
public IQueryable<T> GetFiltered<T>(ReportFilter filter,
Expression<Func<T, int>> clientIdSelector,
Expression<Func<T, int>> appraisalIdSelector)
{
接下来,将这些选择器转换为谓词,将值与我们所拥有的ID进行比较,对于表达式而言,它比常规委托更多。我们真正需要的是Compose
方法;对于委托来说,它很简单,用另一个方法组合一个方法,你只需用参数作为第一个结果来调用它。使用表达式,这意味着获取一个表达式的主体并将该参数的所有实例替换为另一个表达式的主体,然后将整个事物包装在新的Lambda中。
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);
}
这本身取决于将一个表达式的所有实例替换为另一个表达式的能力。为此,我们需要使用以下内容:
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);
}
}
现在我们已经完成了所有这些工作,我们实际上可以通过与过滤器的ID值进行比较来组合我们的选择器:
IQueryable<T> reportQueryable = Context.Set<T>();
reportQueryable = reportQueryable
.Where(clientIdSelector.Compose(id => id == filter.ClientWorkflowId));
if (filter.AppraisalLevelId.HasValue)
{
reportQueryable = reportQueryable
.Where(clientIdSelector.Compose(id => id == filter.AppraisalLevelId.Value));
}
return reportQueryable;