如何处理以下问题?
我们正在使用延迟加载的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<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);
}
}
为简洁起见,省略了GetHashCode
和Equals(object)
的实施。
可以采用哪些策略来解决这个问题?
答案 0 :(得分:10)
如果两个实体属于同一类型且具有相同的主键,则它们是相等的。
如果您有按键的整数:
如果你有钥匙的GUID:
如果我有键的整数,我通常在我的实体的基类中有类似的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);
}
现在,如果您使用每个层次结构的表从其他人继承的实体,您将面临如下问题:GetClassWithoutInitializingProxy将返回层次结构的基类(如果它是代理),如果它是加载的实体则返回更具体的类型。在一个项目中,我通过遍历层次结构来解决这个问题,因此总是比较基本类型 - 代理与否。
在这些日子里,虽然我总是将GUID作为键使用,并按照此处的说明进行操作:http://nhibernate.info/doc/patternsandpractices/identity-field-equality-and-hash-code.html
然后没有代理类型不匹配问题。
答案 1 :(得分: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;
}
}
答案 2 :(得分:1)
我使用以下规则:
如果实体具有POID属性(请记住,不需要属性或任何成员只省略name =“XX”,不确定是否使用activerecord或映射策略supoprt this)
如果实体没有 POID属性,请确保您需要自然ID。使用自然id进行相等和GetHashCode。
如果你有一个多对一的自然id,而不是做FooProperty.Equals(other.FooProperty),请使用FooProperty.Id.Equals(other.FooProperty.Id)。访问ID不会触发惰性引用的初始化。
最后但并非最不重要的一点是,不鼓励使用复合ID,并且非常不鼓励复合ID与多对一密钥。