Лучшие практики для сущностей Entity Framework переопределяют Equals и GetHashCode - PullRequest
0 голосов
/ 30 декабря 2018

Я хочу проверить равенство между двумя сущностями с 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 Ответ

0 голосов
/ 02 января 2019

Entity Framework использует свои собственные интеллектуальные методы для обнаружения равенства объектов.Это, например, используется, если вы вызываете SaveChanges: значения выбранных объектов сопоставляются со значениями обновленных объектов, чтобы определить, требуется ли обновление SQL.

Я не уверен, соответствуют ли ваши определенияравенство нарушит эту проверку на равенство, в результате чего некоторые неизмененные элементы будут обновлены в базе данных, или, что еще хуже, некоторые измененные данные не будут обновлены в базе данных.

Равенство базы данных

Сохранитьпомните, что ваши классы сущностей (классы, которые вы указали в DbSet<...>) представляют таблицы в вашей базе данных и отношения между таблицами.

Когда два элемента, извлеченные из вашей базы данных, должны представлять один и тот же объект?Это когда они имеют одинаковые значения?Разве у нас не может быть двух людей по имени "Джон Доу", родившихся 4 июля в одной базе данных?

Единственный способ определить, что два извлеченных Persons из базы данных представляют один и тот же Person, - это проверить Id.Тот факт, что некоторые значения неосновного ключа отличаются, говорит только о том, что измененные данные не обновляются в базе данных, а не о том, что это Person.

Переопределить Equals vs 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, и вы бы проигнорировали, что они не того же типа, то, возможно,Равно (x, y), но не равно (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);

x и y будут считаться равными

HashSet<Gpu> hashSetIgnoringIds = new HashSet<Gpu>(GpuComparer.IgnoreIdComparer);
hashSetIgnoringIds.Add(x);
bool containsY = hashSetIgnoringIds.Contains(y); // expect true

Сравнение для компьютера будет аналогичным.Помимо того, что вы забыли проверить нулевые значения и типы, я вижу и другие проблемы в том, как вы хотите выполнить проверку на равенство:

  • можно присвоить нулевое значение вашей коллекции 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

...