Как заставить перегруженный оператор == работать с LINQ и EF Core? - PullRequest
0 голосов
/ 05 июня 2019

так что в основном у меня есть проект, который использует EF Core.Чтобы сократить мои лямбды при сравнении, если два объекта (класса Protocol) равны, я переопределил свой метод Equals и перегрузил операторы == и! =.Тем не менее, LINQ, кажется, не заботится об этом, и все еще использует ссылку для определения равенства.Спасибо

Как я уже говорил, я переопределил метод Equals и перегрузил операторы == и! =.Без удачи.Я также попытался реализовать интерфейс IEquatable.Также не повезло.

Я использую: EF Core 2.2.4

// класс протокола

[Key]
public int ProtocolId {get;set;}
public string Repnr {get;set;}
public string Service {get;set;}

public override bool Equals(object obj)
{
    if (obj is Protocol other)
    {
        return this.Equals(other);
    }
    return false;
}

public override int GetHashCode()
{
    return $"{Repnr}-{Service}".GetHashCode();
}

public bool Equals(Protocol other)
{
    return this?.Repnr == other?.Repnr && this?.Service == other?.Service;
}

public static bool operator ==(Protocol lhs, Protocol rhs)
{
    return lhs.Equals(rhs);
}

public static bool operator !=(Protocol lhs, Protocol rhs)
{
    return !lhs.Equals(rhs);
}

// проблема

using (var db = new DbContext())
{

     var item1 = new Protocol() { Repnr = "1666", Service = "180" };
     db.Protocols.Add(item1 );
     db.SaveChanges();
     var item2 = new Protocol() { Repnr = "1666", Service = "180" };
     var result1 = db.Protocols.FirstOrDefault(a => a == item2);
     var result2 = db.Protocols.FirstOrDefault(a => a.Equals(item2));
     //both result1 and result2 are null

}

Я бы ожидал, что и result1, и result2 будут item1.Тем не менее, они оба нулевые.Я знаю, что могу просто сделать a.Repnr == b.Repnr && a.Service == b.Service, но это не так просто.Спасибо

1 Ответ

1 голос
/ 05 июня 2019

Чтобы понять, почему используется неверный компаратор равенства, вы должны знать о разнице между 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 студентов.

...