2011-02-15 76 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; 
    } 
}