2011-05-01 15 views
15

¿Hay alguna razón por la cual Equals o GetHashCode se deben anular en entidades cuando se usa NHibernate? ¿Y en qué escenarios son válidos estos motivos?NHibernate: Razones para anular Iguales y GetHashCode

Algunas de las razones que se pueden encontrar en la web:

  • Soporte para carga diferida. La comparación de objetos proxy de forma predeterminada es igual a método puede conducir a errores inesperados. Pero esto debe resolverse mediante el mapa de identidad (y realmente está en muchos casos), ¿no es así? Al trabajar con entidades de sesión única, todo debería funcionar bien incluso sin anular Equals/GetHashCode. ¿Hay casos en los que el mapa de identidad no cumple bien el rol ?
  • Es importante para las colecciones de NHibernate. ¿Hay algún caso en que la implementación predeterminada de GetHashCode no sea suficiente (sin incluir los problemas relacionados con Igual)?
  • Entidades de mezcla de varias sesiones y entidades separadas. ¿Es buena idea hacerlo?

¿Alguna otra razón?

Respuesta

7

La sobrecarga de los métodos Equals y GetHashCode es importante si está trabajando con múltiples sesiones, entidades separadas, sesiones sin estado o colecciones (¡vea la respuesta de Sixto Saez para ver un ejemplo!).

En la misma sesión, el mapa de identidad de alcance se asegurará de que solo tenga una única instancia de la misma entidad. Sin embargo, existe la posibilidad de comparar una entidad con un proxy de la misma entidad (ver a continuación).

+0

Gracias, es un placer escuchar esa respuesta :). ¿Estás seguro de que el mapa de identidad de sesión no está roto en algunos casos de esquina? ¿Tiene algún ejemplo cuando trabajar con varias sesiones es inevitable? –

+2

Nunca debe obtener 2 objetos diferentes que representen esa misma entidad (como en el mismo tipo de clase y la misma fila de la base de datos) en la misma sesión. De lo contrario, sería un error en NHibernate. En ocasiones, puede utilizar una sesión sin estado (independientemente de la sesión principal) para realizar operaciones de inserción/actualización por lotes o escribir datos de auditoría en la base de datos. Por ejemplo, no puede usar su sesión principal dentro de los interceptores de persistencia. –

+0

@DmitryS. * puede * obtener dos objetos que representan la misma entidad: una entidad proxy en una entidad de colección y no proxy. Si el no proxy hace referencia a la entidad de recopilación, la igualdad de referencia (la predeterminada en NHibernate) ** falla **. Por lo tanto, siempre se recomienda anular iguales. – TheCloudlessSky

12

Como se menciona en su pregunta, identidad de una instancia de entidad es el principal requisito para anular Equals & GetHashCode. Es una buena práctica en NHibernate usar valores de clave numéricos (cortos, int, o largos según corresponda) porque simplifica el mapeo de una instancia a una fila de base de datos. En el mundo de la base de datos, este valor numérico se convierte en el valor de la columna de la clave principal. Si una tabla tiene lo que se denomina una clave natural (donde varias columnas juntas identifican una fila de forma única), un único valor numérico puede convertirse en una clave primaria sustituta para esta combinación de valores.

Si determina que no desea utilizar o no puede utilizar una sola clave primaria numérica, deberá asignar la identidad con NHibernate CompositeKey functionality. En este caso, es absolutamente necesario implementar GetHashCode & Equals anulaciones para que la lógica de comprobación del valor de columna para esa tabla pueda determinar la identidad. Here is a good article al anular el método GetHashCode y Equals. También debe anular el operador igual para estar completo para todos los usos.

Del comentario: ¿En qué casos es insuficiente la implementación predeterminada de Equals (y GetHashCode)?

La implementación predeterminada no es lo suficientemente buena para NHibernate porque está basada en el Object.Equals implementation. Este método determina la igualdad para los tipos de referencia como igualdad de referencia. En otras palabras, ¿estos dos objetos apuntan a la misma ubicación de memoria? Para NHibernate, la igualdad debe basarse en los valores del mapeo de identidad.

Si no lo hace, lo más probable es que se encuentre con la comparación de un proxy de una entidad con la entidad real y será miserable para depurar.Por ejemplo:

public class Blog : EntityBase<Blog> 
{ 
    public virtual string Name { get; set; } 

    // This would be configured to lazy-load. 
    public virtual IList<Post> Posts { get; protected set; } 

    public Blog() 
    { 
     Posts = new List<Post>(); 
    } 

    public virtual Post AddPost(string title, string body) 
    { 
     var post = new Post() { Title = title, Body = body, Blog = this }; 
     Posts.Add(post); 
     return post; 
    } 
} 

public class Post : EntityBase<Post> 
{ 
    public virtual string Title { get; set; } 
    public virtual string Body { get; set; } 
    public virtual Blog Blog { get; set; } 

    public virtual bool Remove() 
    { 
     return Blog.Posts.Remove(this); 
    } 
} 

void Main(string[] args) 
{ 
    var post = session.Load<Post>(postId); 

    // If we didn't override Equals, the comparisons for 
    // "Blog.Posts.Remove(this)" would all fail because of reference equality. 
    // We'd end up be comparing "this" typeof(Post) with a collection of 
    // typeof(PostProxy)! 
    post.Remove(); 

    // If we *didn't* override Equals and *just* did 
    // "post.Blog.Posts.Remove(post)", it'd work because we'd be comparing 
    // typeof(PostProxy) with a collection of typeof(PostProxy) (reference 
    // equality would pass!). 
} 

Aquí es un ejemplo de clase base si está utilizando int como su Id (que también podría ser abstraído a any identity type):

public abstract class EntityBase<T> 
    where T : EntityBase<T> 
{ 
    public virtual int Id { get; protected set; } 

    protected bool IsTransient { get { return Id == 0; } } 

    public override bool Equals(object obj) 
    { 
     return EntityEquals(obj as EntityBase<T>); 
    } 

    protected bool EntityEquals(EntityBase<T> other) 
    { 
     if (other == null) 
     { 
      return false; 
     } 
     // One entity is transient and the other is not. 
     else if (IsTransient^other.IsTransient) 
     { 
      return false; 
     } 
     // Both entities are not saved. 
     else if (IsTransient && other.IsTransient) 
     { 
      return ReferenceEquals(this, other); 
     } 
     else 
     { 
      // Compare transient instances. 
      return Id == other.Id; 
     } 
    } 

    // The hash code is cached because a requirement of a hash code is that 
    // it does not change once calculated. For example, if this entity was 
    // added to a hashed collection when transient and then saved, we need 
    // the same hash code or else it could get lost because it would no 
    // longer live in the same bin. 
    private int? cachedHashCode; 

    public override int GetHashCode() 
    { 
     if (cachedHashCode.HasValue) return cachedHashCode.Value; 

     cachedHashCode = IsTransient ? base.GetHashCode() : Id.GetHashCode(); 
     return cachedHashCode.Value; 
    } 

    // Maintain equality operator semantics for entities. 
    public static bool operator ==(EntityBase<T> x, EntityBase<T> y) 
    { 
     // By default, == and Equals compares references. In order to 
     // maintain these semantics with entities, we need to compare by 
     // identity value. The Equals(x, y) override is used to guard 
     // against null values; it then calls EntityEquals(). 
     return Object.Equals(x, y); 
    } 

    // Maintain inequality operator semantics for entities. 
    public static bool operator !=(EntityBase<T> x, EntityBase<T> y) 
    { 
     return !(x == y); 
    } 
} 
+1

Gracias por su respuesta. Mi pregunta no es lo suficientemente clara. ¿En qué casos es insuficiente la implementación predeterminada de Equals (y GetHashCode)? Cuando trabaje con entidades que están conectadas a una sola sesión, todo debería ir bien gracias a la memoria caché de identidad de NH, a menos que la caché de identidad se rompa de alguna manera (y tengo curiosidad de por qué se puede romper la caché). Mezclar entidades separadas y entidades recuperadas a través de varias sesiones es algo así como correr con tijeras para mí :). ¿Cuándo es necesario hacer tal cosa? –

Cuestiones relacionadas