Как реализовать IEquatable <T>, когда изменяемые поля являются частью равенства - Проблема с GetHashCode

Я использую Entity Framework в своем приложении.

Я реализовал с частичным классом сущности интерфейс IEquatable<T>:

Partial Class Address : Implements IEquatable(Of Address) 'Other part generated
  Public Overloads Function Equals(ByVal other As Address) As Boolean _
      Implements System.IEquatable(Of Address).Equals
    If ReferenceEquals(Me, other) Then Return True
    Return AddressId = other.AddressId
  End Function

  Public Overrides Function Equals(ByVal obj As Object) As Boolean
    If obj Is Nothing Then Return MyBase.Equals(obj)
    If TypeOf obj Is Address Then 
      Return Equals(DirectCast(obj, Address)) 
  Else
    Return False
  End Function

  Public Overrides Function GetHashCode() As Integer
    Return AddressId.GetHashCode
  End Function
End Class

Теперь в моем коде я использую его следующим образом:

Sub Main()
  Using e As New CompleteKitchenEntities
    Dim job = e.Job.FirstOrDefault
    Dim address As New Address()

    job.Addresses.Add(address)
    Dim contains1 = job.Addresses.Contains(address) 'True
    e.SaveChanges()
    Dim contains2 = job.Addresses.Contains(address) 'False

    'The problem is that I can't remove it:
    Dim removed = job.Addresses.Remoeve(address) 'False

  End Using
End Sub

Примечание (я проверил в визуализаторе отладчика), что класс EntityCollection хранит свои объекты в HashSet, поэтому он связан с функцией GetHashCode, я хочу, чтобы она зависела от идентификатора, поэтому сущности сравниваются по их идентификаторам.

Проблема заключается в том, что когда я нажимаю save, идентификатор изменяется от 0 до значения db. Итак, вопрос в том, как я могу иметь равнозначный объект, будучи правильно хэшированным.

Пожалуйста, помогите мне найти то, что неправильно в функции GetHashCode (по идентификатору), и что я могу изменить, чтобы заставить его работать.

Большое спасибо.

Ответ 1

Вы использовали изменчивое поле (AddressId) как часть хэша - это, к сожалению, обречено. Под этим я подразумеваю: когда вы добавляете его, AddressId равен 0? -1? не имеет значения, что именно, но это не окончательный id - и он хранится с этим ключом/хэшем. Когда вы его сохраняете, фактический идентификатор (столбец IDENTITY из базы данных) обновляется в объекте.

Проще говоря, вы не можете надежно хешировать это значение, если оно может измениться, когда оно является частью словаря. Одним из возможных способов решения проблемы было бы рассмотрение единицы работы, то есть вставка - это единица работы. Значение: если данные и т.д. Живут только до тех пор, пока это, то это не проблема, поскольку вы никогда не попытаетесь получить доступ к данным после сохранения. Впоследствии (в другом контексте) загрузка данных также должна быть прекрасной, поскольку идентификатор не изменяется в течение всего срока жизни.

Альтернативно: отбросить этот хэш/равенство.

Ответ 2

Непечатаемые классы не должны реализовывать IEquatable<T>, потому что единственный способ гарантировать, что производный класс, который переопределяет Object.Equals() и Object.GetHashCode(), будет реализовывать IEquatable<BaseType> в соответствии с Object.GetHashCode() для интерфейса для вызова виртуального метода Object.Equals(Object). Поскольку единственной целью IEquatable<T> является избежать накладных расходов при вызове Object.Equals(Object), а безопасная реализация IEquatable<T> в незапечатанном классе не может избежать его вызова, такая реализация не будет иметь никакой цели.

Кроме того, я бы настоятельно советовал против любого переопределения Object.Equals (или реализации IEquatable<T>) для любого типа изменяемого класса. Хорошо для структур, независимо от того, изменяются они или нет, переопределять Object.Equals и Object.GetHashCode и реализовать IEquatable<theirOwnType>, поскольку поля структуры, хранящиеся, например, a Dictionary ключ будет неизменным, даже если тип struct предоставляет изменяемые общедоступные поля.