Почему применяется несколько фильтров, даже если запрос воссоздается на каждой итерации

Я нашел этот код ниже в файле с именем Filter.cs в проекте, созданном с помощью Microsoft App Studio. Хотя я опытный программист на C#, у меня мало опыта работы со строителями предикатных выражений LINQ. Я могу сказать, что приведенный ниже код является "мета-логикой" для гибкого построения запроса с учетом списка предикатов фильтра, содержащих информацию о поле типа и набор значений данных для вставки в подвыражения. Что я не могу понять, так это то, как переменная "expression" в следующем выражении:

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 ответа

Решение

Насколько я понимаю, у вас возникли проблемы с пониманием, почему эта строка выполняется в цикле

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 содержит цепочку, включающую первый член; после второй итерации он содержит два первых члена и т. д.

Код по существу генерирует последовательность вызовов Where/AsQueryable. Не уверен, почему вы ожидаете, что каждый цикл будет добавлять выражения.

По сути результат

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

где я думаю, что вы ожидаете больше как

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

query Имя переменной немного вводит в заблуждение. Этот код не создает длинный фильтр в expression переменной, а затем запустить его для набора данных - каждый фильтр запускается для набора данных по одному, пока все фильтры не будут запущены. query переменная просто содержит все из данных, оставшихся от ранее запущенных фильтров.

Итак, эта строка:

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

применяет фильтр к существующим данным, хранящимся в queryи затем сохранение нового (отфильтрованного) результата обратно в переменную. Значение expression перезаписывается каждый раз в цикле, но он нам больше не нужен, потому что фильтр уже применен.

Он не "стирает", так как цепочки. Он обрабатывает это, присваивая обратно запросу. По сути это похоже на написание:

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

Каждый раз, когда вы звоните .Where(expression).AsQueryable() новый IQueryable<T> возвращается и установлен на query, это IQueryable<T> является результатом последнего .Where вызов. Это означает, что вы эффективно получаете запрос, который выглядит следующим образом:

query.Where(expression1).AsQueryable().Where(expression2).AsQueryable()...
Другие вопросы по тегам