【问题标题】:Why multiple filters are applied even if query recreated on each iteration为什么即使在每次迭代中重新创建查询也应用多个过滤器
【发布时间】:2014-01-24 20:33:46
【问题描述】:

我在使用 Microsoft App Studio 创建的项目中的 Filter.cs 文件中找到了以下代码。虽然我是一名资深的 C# 程序员,但我缺乏使用 LINQ 谓词表达式构建器的经验。我可以说它下面的代码是“元逻辑”,用于在给定包含类型字段信息的过滤谓词列表和要注入子表达式的一组数据值的情况下灵活构建查询。我想不通的是以下语句中的“表达式”变量:

query = query.Where(expression).AsQueryable()" 

.. 将每个字段表达式连接成一个更复杂的查询表达式,最终在代码末尾执行以创建 ObservableCollection 结果。如果它是“query +=”,我可以推断出像事件处理程序字段这样的链接操作,但作为一个直接的赋值语句,它让我感到困惑,因为我希望它替换最后一个值 表达式 变量从上次循环迭代中获得,从而在过程中将其重置并丢失其先前的值。这是怎么回事?

public class Filter<T>
{
    public static ObservableCollection<T> FilterCollection(
        FilterSpecification filter, IEnumerable<T> data)
    {
        IQueryable<T> query = data.AsQueryable();               
        foreach (var predicate in filter.Predicates)
        {
            Func<T, bool> expression;
            var predicateAux = predicate;
            switch (predicate.Operator)
            {
                case ColumnOperatorEnum.Contains:
                    expression = x => predicateAux.GetFieldValue(x).ToLower().Contains(predicateAux.Value.ToString().ToLower());
                    break;
                case ColumnOperatorEnum.StartsWith:
                    expression = x => predicateAux.GetFieldValue(x).ToLower().StartsWith(predicateAux.Value.ToString().ToLower());
                    break;
                case ColumnOperatorEnum.GreaterThan:
                    expression = x => String.Compare(predicateAux.GetFieldValue(x).ToLower(), predicateAux.Value.ToString().ToLower(), StringComparison.Ordinal) > 0;
                    break;
                case ColumnOperatorEnum.LessThan:
                    expression = x => String.Compare(predicateAux.GetFieldValue(x).ToLower(), predicateAux.Value.ToString().ToLower(), StringComparison.Ordinal) < 0;
                    break;
                case ColumnOperatorEnum.NotEquals:
                    expression = x => !predicateAux.GetFieldValue(x).Equals(predicateAux.Value.ToString(), StringComparison.InvariantCultureIgnoreCase);
                    break;
                default:
                    expression = x => predicateAux.GetFieldValue(x).Equals(predicateAux.Value.ToString(), StringComparison.InvariantCultureIgnoreCase);
                    break;
            }

            // Why doesn't this assignment wipe out the expression function value from the last loop iteration?
            query = query.Where(expression).AsQueryable();
        }
        return new ObservableCollection<T>(query);
    }

【问题讨论】:

  • 我已更改标题/标签,因为问题确实没有关于电话应用程序的具体内容。随时恢复/改进。
  • @AlexeiLevenkov 编辑得很好,谢谢。

标签: c# linq ienumerable predicatebuilder


【解决方案1】:

我的理解是你无法理解为什么这行代码会循环执行

query = query.Where(expression).AsQueryable();

产生类似于“连接”表达式的效果。一个简短的答案是它类似于为什么

str = str + suffix;

产生一个更长的字符串,即使它是一个赋值。

更长的答案是循环一次构建一个谓词的表达式,并将Where 附加到条件序列中。尽管它是一个赋值,但它是从对象的先前状态构建的,因此先前的表达式不会“丢失”,因为它被用作更大、更复杂的过滤器表达式的基础。

为了更好地理解它,假设switch 语句生成的各个表达式被放入IQueryable 对象数组中,而不是附加到query 中。一旦构建了零件数组,您就可以这样做:

var query = data.AsQueryable()
    .Where(parts[0]).AsQueryable()
    .Where(parts[1]).AsQueryable()
    ...
    .Where(parts[N]).AsQueryable();

现在观察每个parts[i] 只使用一次;之后,不再需要它。这就是为什么您可以在循环中逐步构建表达式链的原因:在第一次迭代之后,query 包含一个包含第一项的链;在第二次迭代之后,它包含两个第一项,依此类推。

【讨论】:

    【解决方案2】:

    它不会“抹去”,因为它是链接的。它通过分配回查询来处理它。这实际上就像写作:

    var queryTmp = query;
    query = queryTmp.Where(expression).AsQueryable();
    

    每次调用.Where(expression).AsQueryable(),都会返回一个新 IQueryable&lt;T&gt;,并设置为query。这个IQueryable&lt;T&gt; 是最后一次.Where 调用的结果。这意味着您可以有效地得到如下查询:

    query.Where(expression1).AsQueryable().Where(expression2).AsQueryable()...
    

    【讨论】:

      【解决方案3】:

      代码本质上会生成 Where/AsQueryable 调用序列。不知道为什么您希望每个循环都附加表达式。

      本质上是结果

      query = query
        .Where(expression0).AsQueryable()
        .Where(expression1).AsQueryable()
        .Where(expression2).AsQueryable()
      

      我认为你期望更多的地方

      query = query
        .Where(v => expression0(v) && expression1(v) && expression2(v) ...).AsQueryable()
      

      【讨论】:

        【解决方案4】:

        query 变量名有点误导。此代码不会在 expression 变量中构建长过滤器,然后针对数据集运行它 - 它针对数据集运行每个过滤器,一次一个,直到所有过滤器都已运行。 query 变量只包含之前运行的过滤器留下的数据中的所有内容。

        所以这一行:

        query = query.Where(expression).AsQueryable();
        

        正在将过滤器应用于存储在query 中的现有数据,然后将新的(过滤后的)结果保存回变量中。 expression 的值在每次循环中都会被覆盖,但我们不再需要了,因为过滤器已经被应用了。

        【讨论】:

        • "。此代码不会建立一个长过滤器,然后针对数据集运行它" 是的。 IQueryable&lt;T&gt; 使用延迟执行 - 这被转换为在服务器上运行的单个 SQL 语句,并执行 一次。
        • 不,代码一次将一个过滤器应用于数据集,就像函数链一样。这与在 expression 变量中构建一个长过滤器然后使用单个 Where 语句一次性应用它有很大不同。在数据集连接到 SQL 数据库的情况下,延迟执行可以提高性能。但是上述算法仍然一次将一个过滤器提供给数据集,而不管数据集何时选择处理它们。
        • 啊,是的 - 这实际上不是在使用 IQueryable - 它是 IEnumerable - 不过写的很奇怪。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-01-19
        • 2018-10-21
        • 1970-01-01
        • 2013-10-04
        • 1970-01-01
        • 2018-11-27
        相关资源
        最近更新 更多