Equals和GetHashCode中的延迟加载的NHibernate属性

如何处理以下问题?

我们正在使用延迟加载的NHibernate属性,每当我们调用Equals()GetHashCode()任何使用的属性都将延迟加载,可能导致一系列延迟加载操作。 可以使用预先加载作为替代方案,但我认为仅在特定情况下而不是作为一般解决方案。

典型情况如下:

 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 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); } } 

为简洁起见,省略了GetHashCodeEquals(object)实现。

可以用什么策略来解决这个问题?

如果两个实体属于同一类型且具有相同的主键,则它们是相等的。

如果你有键的整数:

  1. 像现在一样检查参考相等性
  2. 如果在某些基类中使用Equal方法,则检查您比较的类型是否相等。 在这里你可以遇到代理问题,我会回到那里
  3. 检查主键是否相等 – 这不会导致任何延迟加载

如果您有密钥的GUID:

  1. 像现在一样检查参考相等性
  2. 检查主键是否相等 – 这不会导致任何延迟加载

如果我有键的整数,我通常在我的实体的基类中有类似的Equal-override:

 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); } 

现在,如果您使用每个层次结构的表inheritance其他实体,您将面临如下问题:GetClassWithoutInitializingProxy将返回层次结构的基类(如果它是代理),如果它是加载的实体则返回更具体的类型。 在一个项目中,我通过遍历层次结构来解决这个问题,因此总是比较基本类型 – 代理与否。

在这些日子里,虽然我总是会使用GUID作为键,并按照此处的描述进行操作: http : //nhibernate.info/doc/patternsandpractices/identity-field-equality-and-hash-code.html

然后没有代理类型不匹配问题。

如果使用标识相等性,则应该能够在不触发加载的情况下访问密钥:

 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; } } 

我使用以下规则:

  1. 如果实体具有POID属性(请记住,不需要属性或任何成员只省略name =“XX”,不确定是否使用activerecord或您使用的映射策略supoprt this)

    • 不是瞬态的:如果实例具有ID!= default(idType),那么如果它们具有相同的id,则它等于另一个实体。
    • 瞬态:如果实例具有ID == default(idType),则如果两者都是相同的Reference,则它等于另一个实体。 ReferenceEquals(this,other)。
  2. 如果实体没有POID属性 ,那么您肯定需要一个自然ID。 使用自然id进行相等和GetHashCode。

  3. 如果你有一个多对一的自然id,而不是做FooProperty.Equals(other.FooProperty),请使用FooProperty.Id.Equals(other.FooProperty.Id)。 访问ID不会触发惰性引用的初始化。

最后但并非最不重要的是,使用composite-id是不受欢迎的,并且复合id与key-many-to-one非常不鼓励。