返回过滤后的IQueryable<;T>;具有匿名类型

本文关键字:类型 gt IQueryable 过滤 lt 返回 | 更新日期: 2023-09-27 18:29:47

我有一组报告,在返回输出之前需要对其进行筛选。我想用一个匿名方法来执行此操作,以避免在不同的存储库中重复相同的代码。我使用的是实体框架,所以模型类型都与数据库相关,并从一个名为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>委托来传递筛选选项this,但Where操作似乎想要返回一个列表。

真的有什么方法可以达到我想要的效果吗?

返回过滤后的IQueryable<;T>;具有匿名类型

  1. 声明一个具有执行此操作所需的两个ID属性的接口
  2. 确保您的实体实现该接口
  3. 向实现该接口的泛型参数添加约束

请注意,如果您的基类定义了这两个有问题的属性,那么您就不需要接口,只需将类型约束到该基类即可:

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;