Почему применяется несколько фильтров, даже если запрос воссоздается на каждой итерации
Я нашел этот код ниже в файле с именем 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()...