Увы, вы забыли написать именно требования ваших групп. Я не могу вычесть его из вашего компаратора, потому что компаратор неправильный.
Согласно вашему компаратору, если x равно null, то x не равно x
IEqualityComparer<int?> comparer = new NullNotEqualComparer();
int? x = null;
bool b = comparer(x, x);
Следовательно, x не будет в той же группе, что и x.
Давайте поговорим позже о компараторе, сначала давайте объясним исключение.
IQueryable не может использовать IEqualityComparer
An IQueryable<...>
имеет Expression
и Provider
. Выражение c представляет в общей форме запрос, который должен быть выполнен; Провайдер знает, какой процесс будет выполнять запрос (обычно это система управления базами данных) и какой язык используется для связи с этим процессом (обычно SQL).
Пока вы объединяете операторы LINQ, возвращающие IQueryable<...>
, меняется только Expression
. К базе данных не обращаются, запрос не выполняется. Только когда вы начинаете перечисление, вызывая GetEnumerator()
(напрямую или глубоко в другой функции, например ToList()
или foreach
), Выражение отправляется Провайдеру, который попытается перевести Выражение в SQL, и выполните запрос. Возвращенные данные представлены в виде IEnumerator<...>
, который вы можете использовать для доступа к возвращенным элементам один за другим.
Проблема в том, что поставщик не знает ваш NullNotEqualComparer
и, следовательно, не может переведите его в SQL. Фактически, существует несколько методов LINQ, которые не поддерживаются LINQ-to-Entities. См. [Поддерживаемые и неподдерживаемые методы (LINQ to Entities)] 1
Поэтому вам придется попытаться поместить сравнение в keySelector GroupBy.
Intermezzo : your NullNotEqualComparer
Ваш компаратор проверки на равенство не является хорошим компаратором. Он не соответствует требованию, чтобы x равнялся x:
IEqualityComparer<int?> comparer = new NullNotEqualComparer();
int? x = null;
bool b = comparer.Equals(x, x);
int? y = x;
bool c = comparer.Equals(x, y);
int? z = null;
bool d = comparer.Equals(x, z);
Что вы ожидаете и каковы результаты?
Практически всегда правильный компаратор Equality начинается с тех же четырех строк :
public bool equals(MyClass x, MyClass y)
{
if (x == null) return y == null; // true if both null, false if x null, y not null
if (y == null) return false; // false, because x != null and y == null
// the following two lines are just for efficiency:
if (object.ReferenceEquals(x, y) return true;
if (x.GetType() != y.GetType()) return false;
// here starts the real comparison:
...
}
В редких случаях вы хотите, чтобы разные типы объектов были одинаковыми. В этом случае вы не будете проверять тип.
GetHashCode используется для быстрой проверки того, что два объекта различны. Если вам нужно сравнить равенство тысячи объектов, и вы легко обнаружите, что 990 из них отличаются, вам нужно полностью проверить только последние 10 элементов.
Представьте себе класс с 20 свойствами. Для полного равенства нужно проверить все 20 свойств. Если вы выберете свой GetHashCode мудро, возможно, нет необходимости проверять все 20 свойств.
Например, если вы хотите найти всех людей, живущих по одному адресу, вам нужно будет проверить страну, город, PostCode, Street, HouseNumber, ...
Быстрый способ исключить большинство людей из вашей входной последовательности - это проверить только PostCode: если у двух людей разные PostCode, они не будут жить на тот же адрес.
Следовательно, единственное требование к вашему GetHashCode: если Equals(x, y)
, то GetHashCode(x) == GetHashCode(y)
. Имейте в виду: не наоборот: могут быть разные x и y, которые имеют одинаковый HashCode. Это легко увидеть: GetHashCode возвращает Int32, поэтому должно быть несколько объектов Int64, которые имеют общий HashCode.
EqualityComparer<int?> comparer = new NullNotEqualComparer();
int? x = null;
int y = comparer.GetHashcode(x); // <== Exception!
Возвращаясь к вашему вопросу
Мне кажется, что вы создали это компаратор равенства, потому что вам нужна отдельная группа для всех элементов в вашей таблице, для которых значение t.NullableInt равно null.
Id NullableInt
A 1
B 2
C 1
D null
E null
F 1
G null
Вам нужны три группы:
- Ключ 1, элементы с Id A, C, F
- Ключ 2, элемент с Id B
- Нулевой ключ, элементы с Id D, E, G
Если это то, что вы хотите, вы можете использовать компаратор по умолчанию для класса Nullable<T>
:
- HasValue для x и y ложны: вернуть true
- HasValue для x и y истинны: вернуть x.Value == y.Value
- во всех остальных случаях: вернуть false.
Предполагая, что у вас есть метод для переведите строки вашего dt.Table в IQueryable:
IQueryable<MyClass> tableRows = db.Table.ToMyClass();
var result = tableRows.GroupBy(row => row.NullableInt);