【问题标题】:Returning filtered IQueryable<T> with anonymous types使用匿名类型返回过滤后的 IQueryable<T>
【发布时间】:2014-11-07 20:38:24
【问题描述】:

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

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

【问题讨论】:

  • ClientWorkflowId 在哪里定义?它是在ReportBase 上定义的,还是您的实体都单独定义ClientWorkflowIdAppraisalLevelId 呢?此外,您尝试将过滤器作为Func&lt;T, bool&gt; 传递可能会失败,因为Queryable.Where 需要Expression&lt;Func&lt;T, bool&gt;&gt;
  • 我放的例子是一个经过大量修剪的版本,我实际上是通过很多属性过滤的。但是,是的,它们都是在 ReportBase 上定义的。所有其他模型都继承了它。

标签: c# entity-framework iqueryable anonymous-types


【解决方案1】:
  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;

【讨论】:

  • 他不能只定义where T: ReportBase吗?从他的通用GetFiltered 方法中,我假设所有ReportBase 继承的报告都继承了这两个ID 属性。
  • @michaelmoore 目前尚不清楚情况是否如此,但 OP 对我的评论的跟进证实了这一点。所以,是的,OP 可以从 Servy 的答案中跳过 #1 和 #2,并通过添加 where T: ReportBase 直接进入 #3。
  • @michaelmoore 如果基类定义了这两个属性,当然可以。我在编辑中澄清了这一点。
猜你喜欢
  • 2012-08-12
  • 2012-01-09
  • 1970-01-01
  • 2010-10-17
  • 1970-01-01
  • 2012-04-24
  • 1970-01-01
  • 2011-07-03
  • 1970-01-01
相关资源
最近更新 更多