2012-03-20 3 views
6

Я заметил, что DbSet.Add() EF довольно медленный. Немного прибегая к помощи объявился ответ, так что обещает до 180x увеличения производительности:Реализовать IEquatable для POCO

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

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

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

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

Это неразумно включать в себя любое изменяемое свойство в хэш-кода, и так как according to MSDN

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

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

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

+0

Почему у ваших объектов есть идентификатор 0? Почему не так, как показывает JomTois в своем примере кода, напрямую назначьте Guid в поле ID/Property. Это идеальная основа для вашего IEquatable, о котором вы упоминаете. Также TomTom указывает методы присвоения идентификатора Range для каждого клиента или использования -1, -2 и -3 в качестве временного идентификатора, эти «решения», похоже, верят в детали. После строительства вы создаете новый Guid и находитесь в бизнесе. Или мне что-то не хватает? –

+0

@YoupTube: Целые числа не могут быть бесполезными. Кроме того, целые числа являются 32-битными, а идентификаторы GUID - 128 бит. Это означает, что SQL Server может вместить в 4 раза больше идентификаторов в памяти, используя Integer (ключ к производительности при выполнении объединений). –

+0

А, я понимаю. Но есть ли у вас множество данных, трафика, соединений и необходимость в сверхвысокой производительности? Это всегда компромисс ... Я знаю. Но использование guid's не должно быть большой проблемой. И тогда вы можете забыть обо всех сложных проблемах, связанных с использованием целых чисел. –

ответ

1

Как и во многих ПОКО, мой объекты изменяемые

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

Создайте HashCode ТОЛЬКО на полях ключа primay.

Равно() должна возвращать истинное IFF участвующие объекты имеют один и тот же хэш-код

Bzzz - Error.

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

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

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

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

+0

Вы правы, отношения между Equals и GetHashCode - это наоборот: «Если два объекта сравниваются как равные, метод GetHashCode для каждого объекта должен возвращать одно и то же значение» http://msdn.microsoft.com/en-us /library/system.object.gethashcode.aspx. Что касается ключа: как только объект вставлен в БД, идентичность и равенство являются простыми. В моем случае я создаю новые объекты и еще не назвал 'SaveChanges()', поэтому все идентификаторы равны 0. Я буду изменять свой вопрос на основе ваших комментариев, но пока не вижу решения, поддерживающего новые объекты без присвоения ПК. –

+1

использует клиентские первичные ключи. GUID # s, последовательность, которую вы создаете на стороне клиента. В противном случае обычные справочные механизмы болезненны. ИЛИ - перейдите к собственным идентификаторам. Мой собственный ORM год назад использовал отрицательные числа (-1, -2, -3) в качестве темпоральных ключей, которые были заменены на вставку. GThe hashcodes в любом случае не могут быть повторно использованы повторно после фиксации (объекты должны обновляться);) Проблема решена. – TomTom

+0

+1, отметив, что равные хэш-коды не автоматически означают, что ваши объекты одинаковы. – VVS

0

Первое первый: К сожалению мой хромой английский :)

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

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

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

MinValue и incremen должно быть важно, потому что EF сортирует Pocos их ПК до совершения изменений в БД и при использовании «-1, -2, -3», POCO которые сохраняются перевернутые, что в некоторых случаях (не в соответствии с каким видом) может быть не идеальным.

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

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

Сравнивая два ПОКО с IEquatable (why does dbset work so slow) затем легко:

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

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

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

Во-первых, все мои 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() в промежутке времени, а поле идентификатора обновлялось базовым классом это одна часть, я не уверен, что это абсолютно правильно, или если она лучше, чем просто Base.GetHashCode()?. Я абстрагировал метод Equals (T другой), чтобы гарантировать, что классы реализации должны были реализовать его каким-либо значимым образом, скорее всего, с полем 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 не равны. В противном случае мы скажем, что они равны, если их свойства одинаковы.

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

=== Edit 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 к реализации. Я использовал Resharper реализацию GetHashCode со всеми свойствами, кроме ID, так как ID мог измениться (не хотите сирот). Это встретит ограничение на равенство в связанном ответе выше.

+1

Это интересный подход. Я попробую, когда смогу, и поделиться своими отзывами. Я был бы признателен за любое дополнительное понимание, которое у вас есть при работе с этой проблемой. –

Смежные вопросы