Чтобы понять, почему используется неверный компаратор равенства, вы должны знать о разнице между IEnumerable<...>
и IQueryable<...>
.
Объект, который реализует IEnumerable<...>
, является объектом, который представляетпоследовательность чего-то.Он содержит все, чтобы получить первый элемент последовательности, и как только вы получите элемент последовательности, вы можете получить следующий элемент, если есть следующий элемент.
Вы начинаете перечислять либо явнопутем вызова GetEnumerator()
и многократного вызова MoveNext()
, или неявным образом, используя foreach
, или операторы завершения LINQ, такие как ToList()
, ToDictionary()
, FirstOrDefault()
, Count()
или Any()
.Последняя группа внутренне использует либо foreach
, либо GetEnumerator()
и MoveNext()
/ Current
.
Объект, который реализует IQueryable<...>
, также представляет перечисляемую последовательность.Разница, однако, заключается в том, что он не (обязательно) содержит все для перечисления.Вместо этого он содержит Expression
и Provider
.Expression
- это общее описание того, что нужно запрашивать.Provider
знает, какой процесс выполнит запрос (обычно это система управления базами данных) и как взаимодействовать с этим процессом (обычно что-то похожее на SQL).
IQueryable<..>
также реализует IEnumerable<..>
, поэтомуВы можете начать перечислять последовательность.Как только вы начинаете перечислять IQueryable<...>
, вызывая (внутренне) GetEnumerator()
, Expression
отправляется Provider
, который переводит Expression
в SQL и выполняет запрос.Результат представлен в виде перечислителя, который можно перечислить с помощью MoveNext()
/ Current
.
Это означает, что если вы хотите перечислить IQueryable<...>
, Expression
должен быть переведен вязык, который поддерживает Provider
.Поскольку компилятор на самом деле не знает, кто будет выполнять запрос, он не может пожаловаться.Вы получите ошибку во время выполнения, если Expression
содержит операторы, которые не могут быть переведены в SQL.
Легко видеть, что SQL не знает ваш собственный определенный Equals
метод.На самом деле, есть даже несколько стандартных функций LINQ, которые не поддерживаются.См. Поддерживаемые и неподдерживаемые методы LINQ (LINQ to Entities) .
Так что мне делать, если я хочу использовать неподдерживаемую функцию?
Одна из вещей, которую вы могли бы сделать, это переместить данные в локальный процесс, а затем вызвать неподдерживаемую функцию.
Это можно сделать с помощью ToList
, но если вы будете использовать только одну или несколькоДля выбранных элементов это может быть пустой тратой вычислительной мощности.
Одна из более медленных частей запроса к базе данных - это передача выбранных данных в локальный процесс.Следовательно, разумно ограничить данные теми данными, которые вы фактически планируете использовать.
Более разумным решением было бы использование AsEnumerable
.Это позволит получить выбранные данные «на страницу».Он извлечет первую страницу, и после того, как вы перечислили через извлеченную страницу (используя MoveNext), он извлечет следующую страницу.
Так что, если вы используете только несколько извлеченных элементов, у вас будетизвлек некоторые элементы, которые не используются, но, по крайней мере, вы не получили бы все из них.
Пример
Предположим, у вас есть функция, которая принимает Student
и возвращает логическое значение
bool HasSpecialAbility(Student student);
Требование: дайте мне трех студентов, живущих в Нью-Йорке, которые имеют особые способности.
Увы, HasSpecialAbility
- это локальная функция, она можетне может быть переведен на Sql.Прежде чем звонить, вам нужно будет передать учащимся ваш местный процесс.
var result = dbContext.Students
// limit the transported data as much as you can:
.Where(student => student.CityCode == "NYC")
// transport to local process per page:
.AsEnumerable()
// now you can call HasSpecialAbility:
.Where(student => HasSpecialAbility(student))
.Take(3)
.ToList();
Хорошо, вы могли получить страницу из 100 учеников, пока вам нужно только 3, но, по крайней мере, вы не получиливсе 25000 студентов.