Реализовать IEquatable для POCO - PullRequest
6 голосов
/ 20 марта 2012

Я заметил, что DFSet.Add () в EF работает довольно медленно. Небольшое приближение к поиску дало SO-ответ, обещающий увеличение производительности в 180 раз:

https://stackoverflow.com/a/7052504/141172

Однако я не совсем понимаю, как реализовать IEquatable<T>, как предлагается в ответе.

Согласно MSDN , если я реализую IEquatable<T>, я также должен переопределить Equals() и GetHashCode().

Как и во многих POCO, мои объекты изменяемы . Перед фиксацией в базе данных (SaveChanges()) новые объекты имеют Id 0. После того, как объекты были сохранены, Id служит идеальной основой для реализации IEquatable, Equals () и GetHashCode ( ).

Не имеет смысла включать любое изменяемое свойство в хеш-код, и, поскольку в соответствии с MSDN

Если два объекта сравниваются как равные, метод GetHashCode для каждого объект должен возвращать то же значение

Должен ли я реализовывать IEquatable<T> как сравнение свойств за свойством (например, this.FirstName == other.FirstName), а не переопределять Equals () и GetHashCode ()?

Учитывая, что мои POCO используются в контексте EntityFramework, следует ли уделять особое внимание полю Id?

Ответы [ 3 ]

3 голосов
/ 17 ноября 2012

Я наткнулся на ваш вопрос в поисках решения того же вопроса. Вот решение, которое я пробую, посмотрите, соответствует ли оно вашим потребностям:

Во-первых, все мои POCO происходят из этого абстрактного класса:

public abstract class BasePOCO <T> : IEquatable<T> where T : class
{
    private readonly Guid _guid = Guid.NewGuid();

    #region IEquatable<T> Members

    public abstract bool Equals(T other);

    #endregion

    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj))
        {
            return false;
        }
        if (ReferenceEquals(this, obj))
        {
            return true;
        }
        if (obj.GetType() != typeof (T))
        {
            return false;
        }
        return Equals((T)obj);
    }

    public override int GetHashCode()
    {
        return _guid.GetHashCode();
    }
}

Я создал поле Guid, доступное только для чтения, которое я использую в переопределении GetHashCode (). Это гарантирует, что если бы я поместил производное POCO в словарь или что-то еще, использующее хеш, я бы не осиротел, если бы я вызвал .SaveChanges () в промежуточный период, а поле ID было обновлено базовым классом Это одна часть, я не уверен, что она полностью правильная, или она лучше, чем просто Base.GetHashCode ()? . Я абстрагировал метод Equals (T other), чтобы гарантировать, что реализующие классы должны были реализовать его каким-либо осмысленным способом, скорее всего с полем ID. Я поместил переопределение Equals (object obj) в этот базовый класс, потому что оно, вероятно, будет одинаковым для всех производных классов.

Это будет реализация абстрактного класса:

public class Species : BasePOCO<Species>
{
    public int ID { get; set; }
    public string LegacyCode { get; set; }
    public string Name { get; set; }

    public override bool Equals(Species other)
    {
        if (ReferenceEquals(null, other))
        {
            return false;
        }
        if (ReferenceEquals(this, other))
        {
            return true;
        }
        return ID != 0 && 
               ID == other.ID && 
               LegacyCode == other.LegacyCode &&
               Name == other.Name;
    }
}

Свойство ID установлено в качестве первичного ключа в базе данных, и EF это знает. Идентификатор равен 0 для вновь созданных объектов, затем устанавливается уникальное положительное целое число в .SaveChanges (). Таким образом, в переопределенном методе Equals (Species other) нулевые объекты, очевидно, не равны, очевидно, одни и те же ссылки, тогда нам нужно только проверить, равен ли ID == 0. Если это так, мы скажем, что два объекта одного типа что оба имеют идентификаторы 0 не равны. В противном случае мы будем говорить, что они равны, если их свойства одинаковы.

Я думаю, что это охватывает все соответствующие ситуации, но, пожалуйста, сообщите, если я ошибаюсь. Надеюсь, это поможет.

=== Редактировать 1

Я думал, что мой GetHashCode () был неправильным, и я посмотрел на этот https://stackoverflow.com/a/371348/213169 ответ относительно предмета. Приведенная выше реализация нарушила бы ограничение на то, что объекты, возвращающие Equals () == true, должны иметь одинаковый хеш-код.

Вот мой второй удар:

public abstract class BasePOCO <T> : IEquatable<T> where T : class
{
    #region IEquatable<T> Members

    public abstract bool Equals(T other);

    #endregion

    public abstract override bool Equals(object obj);
    public abstract override int GetHashCode();
}

И реализация:

public class Species : BasePOCO<Species>
{
    public int ID { get; set; }
    public string LegacyCode { get; set; }
    public string Name { get; set; }

    public override bool Equals(Species other)
    {
        if (ReferenceEquals(null, other))
        {
            return false;
        }
        if (ReferenceEquals(this, other))
        {
            return true;
        }
        return ID != 0 && 
        ID == other.ID && 
        LegacyCode == other.LegacyCode && 
        Name == other.Name;
    }

    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj))
        {
            return false;
        }
        if (ReferenceEquals(this, obj))
        {
            return true;
        }
        return Equals(obj as Species);
    }

    public override int GetHashCode()
    {
        unchecked
        {
            return ((LegacyCode != null ? LegacyCode.GetHashCode() : 0) * 397) ^ 
                   (Name != null ? Name.GetHashCode() : 0);
        }
    }

    public static bool operator ==(Species left, Species right)
    {
        return Equals(left, right);
    }

    public static bool operator !=(Species left, Species right)
    {
        return !Equals(left, right);
    }
}

Итак, я избавился от Guid в базовом классе и переместил GetHashCode в реализацию. Я использовал реализацию GetHashCode от Resharper со всеми свойствами, кроме ID, так как ID может измениться (не нужно оставлять сирот). Это встретит ограничение на равенство в связанном ответе выше.

1 голос
/ 20 марта 2012

Как и во многих POCO, мои объекты изменчивы

Но они НЕ должны изменяться в полях, являющихся первичным ключом. По определению, или вы все равно попадаете в мир болевых баз данных.

Генерация HashCode ТОЛЬКО на полях первичного ключа.

Функция Equals () должна возвращать истину, если участвующие объекты имеют одинаковый хэш-код

BZZZ - Ошибка.

Хэш-коды двойные. 2 объекта могут иметь разные значения и хэш-код smae. Hsahsode - это int (32 бита). Строка может быть длиной 2 ГБ. Вы не можете отобразить каждую возможную строку в отдельный хеш-код.

Если два объекта имеют одинаковый хэш-код, они могут различаться. Если два объекта одинаковы, они НЕ могут иметь разные хеш-коды.

Откуда вы взяли, что Equals должен возвращать true для объектов с одинаковым хеш-кодом?

Кроме того, PCO или нет, объект, отображаемый в базу данных и используемый в отношении, ДОЛЖЕН иметь стабильный первичный ключ (который можно использовать для выполнения вычисления хэш-кода). Объект, не имеющий этого STIL, должен иметь первичный ключ (в соответствии с требованиями SQL Server), здесь используется последовательность / искусственный первичный ключ. Снова используйте это для запуска вычисления HashCode.

0 голосов
/ 27 апреля 2012

Перво-наперво: Извините, мой хромой английский:)

Как говорят TomTom, они не должны быть изменяемыми только потому, что они все еще не получили PK / Id ...

В нашей системе EF: CF мы используем сгенерированный отрицательный идентификатор (назначенный в базовом классе ctor или, если вы используете ProxyTracking, в событии ObjectMaterialized) для каждого нового POCO. Это довольно простая идея:

public static class IdKeeper
{
  private static int m_Current = int.MinValue;
  private static Next()
  {
    return ++m_Current;
  }
}

MinValue и incremen должны быть важны, потому что EF будет сортировать POCO по их PK перед фиксацией изменений в db, а когда вы используете «-1, -2, -3», POCO сохраняются перевернутыми, что в некоторых случаях (не соответствует к какому виду) не может быть идеальным.

public abstract class IdBase
{
  public virtual int Id { get; set; }
  protected IdBase()
  {
    Id = IdKeeper.Next();
  }
}

Если POCO материализуется из БД, его Id будет переопределен с фактическим PK, а также при вызове SaveChanges (). И в качестве бонуса, каждый «еще не сохраненный» идентификатор POCO будет уникальным (это должно пригодиться однажды;))

Сравнение двух POCO с IEquatable ( почему dbset работает так медленно ) тогда просто:

public class Person
  : IdBase, IEquatable<Person>
{
  public virtual string FirstName { get; set; }

  public bool Equals(Person other)
  {
    return Id == other.Id;
  }
}
...