Лучшие практики для сущностей Entity Framework переопределяют Equals и GetHashCode
Я хочу проверить равенство между двумя объектами с one-to-many
отношения внутри них.
Так что, очевидно, я преодолел Object.Equals
метод, но затем я получаю предупреждение компилятора CS0659: 'class' overrides Object.Equals(object o) but does not override Object.GetHashCode()
,
Я преодолел Object.GetHashCode
Но потом Решарпер сказал мне, что GetHashCode
Метод должен возвращать один и тот же результат для всего жизненного цикла объекта и будет использоваться в изменяемых объектах. ( документы)
public class Computer
{
public long Id { get; set; }
public ICollection<GPU> GPUs { get; set; } = new List<GPU>();
public override bool Equals(object obj)
{
return obj is Computer computer &&
GPUs.All(computer.GPUs.Contains);
}
public override int GetHashCode()
{
return HashCode.Combine(GPUs);
}
}
public class GPU
{
public long Id { get; set; }
public int? Cores { get; set; } = null;
public override bool Equals(object obj)
{
return obj is GPU gpu &&
Cores == gpu.Cores;
}
public override int GetHashCode()
{
return HashCode.Combine(Cores);
}
}
Я не знаю, что я предпочитаю:
- Переопределение
Equals
метод без переопределенияGetHashCode
, или же - Переопределение
GetHashCode
с неизменными данными?
1 ответ
Entity Framework использует свои собственные интеллектуальные методы для обнаружения равенства объектов. Это, например, используется, если вы звоните SaveChanges
: значения выбранных объектов сопоставляются со значениями обновленных объектов, чтобы определить, требуется ли обновление SQL или нет.
Я не уверен, что ваши определения равенства будут мешать этой проверке равенства, вызывая обновление некоторых неизмененных элементов в базе данных, или, что еще хуже, некоторые измененные данные не будут обновляться в базе данных.
Равенство базы данных
Имейте в виду, что ваши классы сущностей (классы, которые вы помещаете в DbSet<...>
) представляют таблицы в вашей базе данных и отношения между таблицами.
Когда два элемента, извлеченные из вашей базы данных, должны представлять один и тот же объект? Это когда они имеют одинаковые значения? Разве у нас не может быть двух людей по имени "Джон Доу", родившихся 4 июля в одной базе данных?
Единственный способ, которым вы можете использовать, чтобы обнаружить, что два извлеченных Persons
из базы данных представляют собой то же самое Person
это путем проверки Id. Тот факт, что некоторые значения неосновного ключа различаются, говорит только о том, что измененные данные не обновляются в базе данных, а не о том, что Person
,
Переопределить Equals против Create EqualityComparer
Я бы посоветовал сделать ваши представления таблиц максимально простыми: только столбцы таблицы (не виртуальные свойства) и отношения между таблицами (виртуальные свойства). Нет членов, нет методов, ничего.
Если вам нужна дополнительная функциональность, создайте функции расширения классов. Если вам нужны нестандартные методы сравнения на равенство, создайте отдельный инструмент для сравнения на равенство. Пользователи вашего класса могут решить, хотят ли они использовать метод сравнения по умолчанию или ваш специальный метод сравнения.
Все это сопоставимо с различными типами String Comparers: StringComparer.OrdinalIgnorCase
, StringComparer.InvariantCulture
, так далее.
Вернуться к вашему вопросу
Мне кажется, что вам нужен компаратор Gpu, который не проверяет значение Id: два элемента, которые имеют разные Id, но одинаковые значения для других свойств считаются равными.
class GpuComparer : EqualityComparer<Gpu>
{
public static IEqualityComparer<Gpu> IgnoreIdComparer {get;} = new GpuComparer()
public override bool Equals(Gpu x, Gpu y)
{
if (x == null) return y == null; // true if both null, false if x null but y not
if (y == null) return false; // because x not null
if (Object.ReferenceEquals(x, y)) return true;
if (x.GetType() != y.GetType()) return false;
// if here, we know x and y both not null, and of same type.
// compare all properties for equality
return x.Cores == y.Cores;
}
public override int GetHasCode(Gpu x)
{
if (x == null) throw new ArgumentNullException(nameof(x));
// note: I want a different Hash for x.Cores == null than x.Cores == 0!
return (x.Cores.HasValue) ? return x.Cores.Value.GetHashCode() : -78546;
// -78546 is just a value I expect that is not used often as Cores;
}
}
Обратите внимание, что я добавил тест для того же типа, потому что если y является производным классом Gpu, и вы игнорируете, что они не одного типа, то, возможно, Equals(x, y), но не Equals(y, x), которая является одной из предпосылок функций равенства
Использование:
IEqualityComparer<Gpu> gpuIgnoreIdComparer = GpuComparer.IgnoreIdComparer;
Gpu x = new Gpu {Id = 0, Cores = null}
Gpu y = new Gpu {Id = 1, Cores = null}
bool sameExceptForId = gpuIgnoreIdComparer.Equals(x, y);
х и у будут считаться равными
HashSet<Gpu> hashSetIgnoringIds = new HashSet<Gpu>(GpuComparer.IgnoreIdComparer);
hashSetIgnoringIds.Add(x);
bool containsY = hashSetIgnoringIds.Contains(y); // expect true
Компаратор для компьютера будет похожим. Помимо того, что вы забыли проверить на null и типы, я вижу некоторые другие проблемы в том, как вы хотите выполнить проверку на равенство:
- можно присвоить null вашей коллекции Gpus. Вы должны решить это, чтобы оно не бросало исключение. Компьютер с нулевым Gpus равен компьютеру с нулевым Gpus?
- Видимо порядок Gpus для вас не важен: [1, 3] равен [3, 1]
- Видимо, количество раз, когда появляется определенный графический процессор, не имеет значения: [1, 1, 3] равно [1, 3, 3]?
,
class IgnoreIdComputerComparer : EqualityComparer<Computer>
{
public static IEqualityComparer NoIdComparer {get} = new IgnoreIdComputerCompare();
public override bool (Computer x, Computer y)
{
if (x == null) return y == null;not null
if (y == null) return false;
if (Object.ReferenceEquals(x, y)) return true;
if (x.GetType() != y.GetType()) return false;
// equal if both GPU collections null or empty,
// or any element in X.Gpu is also in Y.Gpu ignoring duplicates
// using the Gpu IgnoreIdComparer
if (x.Gpus == null || x.Gpus.Count == 0)
return y.Gpus == null || y.Gpus.Count == 0;
// equal if same elements, ignoring duplicates:
HashSet<Gpu> xGpus = new HashSet<Gpu>(x, GpuComparer.IgnoreIdComparer);
return xGpush.EqualSet(y);
}
public override int GetHashCode(Computer x)
{
if (x == null) throw new ArgumentNullException(nameof(x));
if (x.Gpus == null || x.Gpus.Count == 0) return -784120;
HashSet<Gpu> xGpus = new HashSet<Gpu>(x, GpuComparer.IgnoreIdComparer);
return xGpus.Sum(gpu => gpu);
}
}
TODO: если вы будете использовать большие коллекции Gpus, подумайте о более умном GetHashCode