2011-05-01 72 views
15

當使用NHibernate時,爲什麼Equals或GetHashCode應該在實體中被覆蓋?這些原因在哪些情況下有效?NHibernate:覆蓋Equals和GetHashCode的原因

可以在網上找到一些原因:

  • 支持延遲加載。通過默認等於 方法比較 代理對象可能會導致意外的錯誤。 但是這個問題應該可以通過 的身份圖解決(而且它的確在 的很多情況下),應該不是嗎?當處理來自單個會話的實體時,即使不重寫Equals/GetHashCode,一切都應該正常工作。有沒有 任何情況下當身份圖 不能很好地發揮它的作用?
  • 這對NHibernate集合很重要。是否有任何情況下,默認實現GetHashCode是不夠的(不包括等於相關的問題)?
  • 混合來自幾個 會話和分離實體的實體。這是否是 的好主意?

其他原因?

回答

7

重載EqualsGetHashCode方法是非常重要的如果您正在使用多個會話,分離的實體,無狀態會話或集合(請參閱Sixto Saez的答案)。

在同一會話範圍內,身份映射將確保您只有同一個實體的單個實例。但是,可以將實體與同一實體的代理進行比較(參見下文)。

+0

謝謝,很高興聽到這樣的答案:)。你確定會話身份地圖在某些角落案例中沒有被破解嗎?你有沒有任何例子,當幾個會議的工作是不可避免的? – 2011-05-03 07:47:06

+2

您不應該在同一個會話中獲得代表同一實體的兩個不同對象(如同一類類型和同一數據庫行)。否則,這將是NHibernate中的一個錯誤。有時你可以使用無狀態會話(獨立於主會話)進行批量插入/更新操作或將審計數據寫入數據庫。例如,你不能在持久性攔截器中使用你的主會話。 – 2011-05-03 14:51:31

+0

@DmitryS。你*可以*得到兩個代表同一實體的對象:一個集合和非代理實體中的代理實體。如果非代理引用集合實體,引用相等(NHibernate中的默認值)**失敗**。因此,總是建議覆蓋等於。 – TheCloudlessSky 2013-11-20 23:46:55

12

正如你在你的問題提了,一個實體實例的身份是覆蓋Equals & GetHashCode主要要求。在NHibernate中,最好使用數字鍵值(short,int或long,如適用),因爲它簡化了將實例映射到數據庫行。在數據庫世界中,這個數值成爲主鍵列值。如果一個表具有所謂的自然鍵(其中幾個列一起唯一地標識一行),則單個數值可以成爲該組合值的代理主鍵。

如果您確定不想使用或被阻止使用單個數字主鍵,那麼您需要使用NHibernate CompositeKey functionality映射標識。在這種情況下,您絕對需要實施自定義的覆蓋,以便該表的列值檢查邏輯可以確定標識。 Here is a good article覆蓋GetHashCodeEquals方法。您還應該覆蓋平等運算符,以便爲所有用法完成。

來自評論:在哪些情況下默認執行Equals(和GetHashCode)不足?

對於NHibernate,默認實現不夠好,因爲它基於Object.Equals implementation。該方法確定參考類型的平等作爲參考平等。換句話說,這兩個對象是指向相同的內存位置嗎?對於NHibernate,等式應該基於身份映射的值。

如果你不這樣做,你很有可能會碰到一個實體代理與真實實體的比較,它將是慘不忍睹調試。例如:

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

下面是一個例子基類,如果你使用int爲您Id(這也可以被抽象爲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

感謝您的回答。我的問題還不夠清楚。在哪些情況下默認實現Equals(和 GetHashCode)不夠?當處理連接到單個會話的實體時,除非身份緩存以某種方式中斷(並且我很好奇爲什麼緩存可能會被破壞),否則一切都應該順利地通過NH身份緩存 。通過 幾個會話混合分離的實體和實體就像用剪刀爲我運行:)。什麼時候需要做這樣的事情? – 2011-05-02 14:29:23

相關問題