2012-06-29 13 views
9

Hay muchas preguntas y respuestas y artículos a esta pregunta disponibles pero en mi opinión no parece haber ninguna respuesta real clara/correctaIguales ¿Cómo debemos ser realmente implenting y GetHashCode para las entidades de NHibernate

Para mí Ayende tiene la mejor implementación genérica hasta ahora que he visto: http://ayende.com/blog/2500/generic-entity-equality

.... Pero es a partir de 2007 ....

este es el 'mejor camino' para poner en práctica estos métodos especialmente con respecto a NHibernate 3.2 que contiene algunas diferencias en la implementación del proxy tación a versiones anteriores?

+0

Por favor marque la respuesta SÍ como la respuesta correcta . –

Respuesta

2

Mi recomendación personal es no implementar estos métodos en absoluto, ya que al hacerlo se fuerza la carga en muchos casos donde no es realmente necesario.

Además, si no mueve entidades entre sesiones, nunca necesitará esto. E incluso si lo hace, siempre puede comparar por Id cuando sea necesario.

+0

¿Qué pasa cuando tienes una colección de proxies con carga diferida y un proxy no proxy? Las comparaciones con esa colección ** fallarán **. – TheCloudlessSky

+0

@thecloudlesssky simplemente compare por id manualmente. Puede usar LINQ para objetos para una proyección si lo necesita. –

+0

Claro, pero no puede controlar cómo 'collection.Remove (entity)' hace comparaciones. Mira la muestra en mi publicación a continuación para ver cómo podría fallar. – TheCloudlessSky

5

Sí!

Debería estar anulando Equals y GetHashCode. Pero, usted no debería estar haciendo igualdad de valor (Name == other.Name && Age == other.Age), ¡debería estar haciendo la igualdad 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

Si una entidad transitoria toma su 'GetHashCode' y no es transitoria, puede compararse a otra entidad que no haya tenido su código hash tomado mientras era transitorio, pero su código hash no será igual al de la otra entidad.Cada vez que un objeto se vuelve igual a algo que no era antes, su código hash debe ser igual al código hash de ese otro objeto, y cada vez que un objeto llega a existir que es igual a otro cuyo hash ha sido tomado, su hash debe ser igual al de el otro objeto. Lo que puede ser necesario es tener un 'ConcurrentDictionary ' y ... – supercat

+1

... hacer que cada 'EntityBase ' contenga una referencia a un 'Objeto' asociado con ese id; si se llama a 'GetHashCode' antes de que se persista una entidad, podría generar un' Objeto' que luego se asociaría con la ID que recibe cuando se persista el objeto. Entonces podría usar el valor 'GetHashCode' de ese objeto como propio, y las entidades futuras que tengan el ID que transitorio eventualmente reciba podrían obtener el mismo token de identidad. Periódicamente, la tabla debería borrarse de las entradas cuyo 'WeakReference' había muerto. – supercat

+0

@supercat ¿Te importaría crear un ejemplo de lo que estás proponiendo? La implementación que tengo arriba es la recomendación habitual de los artículos de NH. ¿Podría también proporcionar una muestra rápida de cómo podría fallar? ¡Gracias! – TheCloudlessSky

Cuestiones relacionadas