为什么即使在每次迭代时重新创建查询,也会应用多个过滤器

时间:2014-01-24 20:33:46

标签: c# linq ienumerable predicatebuilder

我在使用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);
    }

4 个答案:

答案 0 :(得分:4)

我的理解是你无法理解为什么这一行在循环中执行

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包含一个包含第一个术语的链;在第二次迭代之后,它包含两个第一项,依此类推。

答案 1 :(得分:3)

它不会“擦除”,因为它 链接。它通过分配回查询来处理它。这有点像写作:

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

每次致电.Where(expression).AsQueryable()时,都会返回 IQueryable<T>,并设置为query。此IQueryable<T>是上次.Where来电的结果。这意味着您可以有效地获得如下所示的查询:

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

答案 2 :(得分: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()

答案 3 :(得分:3)

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

所以这一行:

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

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