使用匿名类型返回已过滤的IQueryable <T>

Ami*_*ble 2 c# entity-framework iqueryable anonymous-types

我有一组报告,我需要在返回输出之前执行过滤.我想用一个匿名方法执行此操作,以避免在不同的存储库中复制相同的代码.我正在使用Entity Framework,因此模型类型都与数据库相关,并从一个名为的基类继承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;
}
Run Code Online (Sandbox Code Playgroud)

我一直试图匿名实现这个,所以我可以重用它,就像在这个非功能性的例子中一样.

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;
}
Run Code Online (Sandbox Code Playgroud)

我遇到的问题当然是使用Where模糊,所以无法解决p.ClientWorkflowId.

我已经尝试使用Func<T, TResult>委托传递过滤选项,但Where操作似乎想要返回一个列表.

实际上是否有一种方法可以用来实现我想要的效果?

Ser*_*rvy 5

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

请注意,如果您的基类定义了两个有问题的属性,那么您不需要接口,并且可以简单地将类型约束到该基类:

public IQueryable<T> GetFiltered<T>(ReportFilter filter) where T : ReportBase
{
    // body unchanged
}
Run Code Online (Sandbox Code Playgroud)

如果你想沿着接受参数的路线去表示这些属性,那么它也是可能的.第一件事是你需要接受表达式而不是Func对象,以便查询提供者可以分析它们.这意味着将函数签名更改为:

public IQueryable<T> GetFiltered<T>(ReportFilter filter,
    Expression<Func<T, int>> clientIdSelector,
    Expression<Func<T, int>> appraisalIdSelector)
{
Run Code Online (Sandbox Code Playgroud)

接下来,将这些选择器转换为谓词,将值与我们所拥有的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);
}
Run Code Online (Sandbox Code Playgroud)

这本身取决于将一个表达式的所有实例替换为另一个表达式的能力.为此,我们需要使用以下内容:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

现在我们已经完成了所有这些工作,我们实际上可以通过与过滤器的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;
Run Code Online (Sandbox Code Playgroud)