2011-02-15 47 views
5

如何處理以下問題?Equals和GetHashCode中的惰性加載的NHibernate屬性

我們使用懶加載NHibernate特性,每當我們調用Equals()GetHashCode()使用任何屬性,將延遲加載,可能導致延遲加載操作的級聯。急切加載可以用作替代,但我認爲只有在特定情況下,而不是作爲一般解決方案。

一個典型的情況是這樣的:

實施 GetHashCodeEquals(object)
public class AbstractSaveableObject { 
    [Id(0, Name = "Id", UnsavedValue = null)] 
    [Generator(1, Class = "native")] 
    public virtual long? Id { get; set; } 
} 

[Class(NameType = typeof(ClassA))] 
public class ClassA : AbstractSavableObject { 
    [Bag(0, Inverse = true, Cascade = "none")] 
    [Key(1, Column = "ClassA")] 
    [OneToMany(2, ClassType = typeof(ClassB))] 
    public virtual ICollection<ClassB> ClassBs { get; set; } 
} 

[Class(NameType = typeof(ClassB))] 
public class ClassB : AbstractSavableObject { 

    [ManyToOne(Column = "ClassA")] 
    public virtual ClassA ClassA { get; set; } 

    [ManyToOne] 
    public virtual ClassC ClassC { get; set; } 

    [ManyToOne] 
    public virtual ClassD ClassD { get; set; } 

    public virtual bool Equals(ClassB other) 
    { 
     if (ReferenceEquals(null, other)) 
     { 
      return false; 
     } 
     if (ReferenceEquals(this, other)) 
     { 
      return true; 
     } 
     return Equals(other.ClassC, ClassC) && Equals(other.ClassD, ClassD); 
    } 
} 

已爲簡潔起見省略。

有什麼策略可以用來解決這個問題?

+0

您的實體上沒有任何主鍵嗎? – asgerhallas 2011-02-15 12:05:35

+0

是的,我做了,我省略了它們,實際上所有持久化類都來自包含代理主鍵的抽象基類。 – 2011-02-15 12:24:29

回答

10

如果兩個實體的類型相同並且具有相同的主鍵,則它們是相等的。

如果你有鑰匙的整數:

  1. 檢查引用相等像你現在要做的
  2. 如果在您檢查,您比較的類型相同的一些基類中的平等法。在這裏,您可以得到麻煩與代理,我將返回到
  3. 檢查主鍵是平等的 - 如果你有GUID的鑰匙,不會造成任何延遲加載

  1. 檢查引用相等像你現在要做的
  2. 檢查主鍵是相等的 - 這不會造成任何延遲加載

如果我有鑰匙整數我平時都在一個基類像這樣的等倍率爲我的實體:

public virtual bool Equals(EntityBase other) 
{ 
    if (other == null) 
    { 
     return false; 
    } 

    if (ReferenceEquals(other, this)) 
    { 
     return true; 
    } 

    var otherType = NHibernateProxyHelper.GetClassWithoutInitializingProxy(other); 
    var thisType = NHibernateProxyHelper.GetClassWithoutInitializingProxy(this); 
    if (!otherType.Equals(thisType)) 
    { 
     return false; 
    } 

    bool otherIsTransient = Equals(other.Id, 0); 
    bool thisIsTransient = Equals(Id, 0); 
    if (otherIsTransient || thisIsTransient) 
     return false; 

    return other.Id.Equals(Id); 
} 

現在,如果你的實體,從他人使用每一個分層表,你將面臨GetClassWithoutInitializingProxy將返回基地的問題繼承如果它是一個代理,則是層次結構的類;如果它是一個加載的實體,則是更具體的類型。在一個項目中,我通過遍歷層次結構來解決這個問題,因此總是比較基本類型 - 代理與否。

在這些日子裏,雖然我總是會去使用的GUID作爲鍵並做如下描述:http://nhibernate.info/doc/patternsandpractices/identity-field-equality-and-hash-code.html

那麼有沒有代理類型不匹配的問題。

+0

但暫時的實體沒有主鍵(因爲我使用代理主鍵,這是在持久化時由nhibernate設置的),所以在持久化之前和之後實體不會等於自己。這不會違反「Equals」合同嗎? – 2011-02-15 12:27:39

1

我用下面的規則:

  1. 如果實體有一個POID屬性(請記住,有沒有必要的財產或任何成員只是省略名稱=「XX」,不知道是否ActiveRecord的或映射策略您正在使用supoprt本)

    • 不是暫時的:如果實例具有ID =默認(idType),那麼它是等於另一個實體如果兩者都具有相同的ID。
    • 瞬態:如果實例具有ID == default(idType),那麼它等於另一個實體,如果兩者都是相同的Reference。 ReferenceEquals(this,other)。
  2. 如果實體不具有POID財產,肯定你會需要一個自然的ID。使用自然編號進行平等和GetHashCode。

  3. 如果您有多對一的自然標識,而不是使用FooProperty.Equals(other.FooProperty),請使用FooProperty.Id.Equals(other.FooProperty.Id)。訪問ID不會觸發惰性引用的初始化。

最後但並非最不重要的,使用複合-ID是勸阻,並用鑰匙多到一個複合的ID很勸阻。

2

如果使用的是身份平等,你應該能夠在不觸發負載訪問密鑰:

public virtual bool Equals(ClassB other) 
{ 
    if (ReferenceEquals(null, other)) 
    { 
     return false; 
    } 
    if (ReferenceEquals(this, other)) 
    { 
     return true; 
    } 
    // needs to check for null Id 
    return Equals(other.ClassC.Id, ClassC.Id) && Equals(other.ClassD.Id, ClassD.Id); 
} 

可以前處理對象進行比較,並通過緩存的哈希碼時,它是短暫的持續後。這在Equals合同中留下了一個小差距,即在一個現有的臨時對象之間進行比較將不會生成與同一對象的新檢索版本相同的哈希碼。

public abstract class Entity 
{ 
    private int? _cachedHashCode; 

    public virtual int EntityId { get; private set; } 

    public virtual bool IsTransient { get { return EntityId == 0; } } 

    public override bool Equals(object obj) 
    { 
     if (obj == null) 
     { 
      return false; 
     } 
     var other = obj as Entity; 
     return Equals(other); 
    } 

    public virtual bool Equals(Entity other) 
    { 
     if (other == null) 
     { 
      return false; 
     } 
     if (IsTransient^other.IsTransient) 
     { 
      return false; 
     } 
     if (IsTransient && other.IsTransient) 
     { 
      return ReferenceEquals(this, other); 
     } 
     return EntityId.Equals(other.EntityId); 
    } 

    public override int GetHashCode() 
    { 
     if (!_cachedHashCode.HasValue) 
     { 
      _cachedHashCode = IsTransient ? base.GetHashCode() : EntityId.GetHashCode(); 
     } 
     return _cachedHashCode.Value; 
    } 
}