2012-06-29 13 views
9

Ci sono molte domande e risposte e articoli a questa domanda disponibile ma a mio parere non sembra esserci alcuna vera risposta chiara/correttaCome dovremmo davvero essere implenting Equals e GetHashCode per le entità NHibernate

Per me Ayende ha la migliore implementazione generica così lontano che ho visto: http://ayende.com/blog/2500/generic-entity-equality

.... Ma è dal 2007 ....

è questo il 'modo migliore' per implementare questi metodi in particolare per quanto riguarda NHibernate 3.2 che contiene alcune differenze nell'implementazione proxy risposta alle versioni precedenti?

+0

Si prega di indicare la risposta SI come la risposta corretta . –

risposta

2

La mia raccomandazione personale non è quella di implementare questi metodi affatto, perché così forzare il caricamento in molti casi in cui non è realmente necessario.

Inoltre, se non si spostano le entità tra le sessioni, non sarà mai necessario. E anche se lo fai, puoi sempre confrontare per ID quando necessario.

+0

Che dire quando si dispone di una raccolta di proxy e di un proxy non pigro? I confronti con quella raccolta ** falliranno **. – TheCloudlessSky

+0

@thecloudlesssky basta confrontare per ID manualmente. Puoi usare LINQ per oggetti per una proiezione se ne hai bisogno. –

+0

Certo, ma non puoi controllare come 'collection.Remove (entity)' fa confronti. Guarda l'esempio nel mio post qui sotto per vedere come questo potrebbe fallire. – TheCloudlessSky

5

Sì!

È necessario ignorare Equals e GetHashCode. Ma, non dovresti fare uguaglianza di valore (Name == other.Name && Age == other.Age), dovresti fare l'uguaglianza di identità!

Se non lo fai, è molto probabile incorrere in confrontando un proxy di un'entità con l'entità vera e sarà miserabile per eseguire il debug. Per esempio:

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!). 
} 

Ecco un esempio di classe di base se si sta utilizzando int come Id (che potrebbe anche essere estratta 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

Se un'entità transitoria ha il suo 'GetHashCode' preso e diventa non-transitorio, può essere paragonato ad un'altra entità che non ha avuto il suo codice hash mentre era transitoria, ma il suo codice hash non è uguale a quello dell'altra entità.Ogni volta che un oggetto diventa uguale a qualcosa che non era prima, il suo codice hash deve essere uguale al codice hash di un altro oggetto e ogni volta che un oggetto viene messo in esistenza che è uguale a un altro il cui hash è stato preso, il suo hash deve essere uguale a quello di l'altro oggetto. Quello che potrebbe essere necessario è avere un 'ConcurrentDictionary ' e ... – supercat

+1

... avere ciascuno 'EntityBase 'mantenere un riferimento a un' Oggetto' associato a quell'id; se 'GetHashCode' viene chiamato prima che un'entità sia persistente, potrebbe generare un' Object' che sarà quindi associato all'ID che riceve quando l'oggetto viene mantenuto. Potrebbe quindi utilizzare il valore dell'oggetto 'GetHashCode' come proprio, e le entità future che hanno l'ID che il transitorio alla fine riceve potrebbero ottenere lo stesso token di identità. Periodicamente la tabella dovrebbe essere cancellata da voci il cui 'WeakReference' era morto. – supercat

+0

@supercat Ti dispiacerebbe creare un esempio di ciò che stai proponendo? L'implementazione che ho sopra è la solita raccomandazione dagli articoli di NH. Potresti fornire anche un rapido esempio di come potrebbe fallire? Grazie! – TheCloudlessSky

Problemi correlati