简体   繁体   English

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

[英]Lazy-loaded NHibernate properties in Equals and GetHashCode

How can the following problem be dealt with? 如何处理以下问题?

We're using lazy loaded NHibernate properties and whenever we're calling Equals() or GetHashCode() any properties used, will be lazy-loaded, potentially causing a cascade of lazy-loading operations. 我们正在使用延迟加载的NHibernate属性,每当我们调用Equals()GetHashCode()任何使用的属性都将被延迟加载,可能导致一连串的延迟加载操作。 Eager-loading could be used as an alternative, but I think only in specific cases and not as a general solution. 可以使用急切加载作为替代方案,但我认为仅在特定情况下而不是作为一般解决方案。

A typical scenario would look like this: 典型情况如下:

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

Implementation of GetHashCode and Equals(object) have been omitted for brevity. 为简洁起见,省略了GetHashCodeEquals(object)实现。

What strategies can be been used to tackle this issue? 可以用什么策略来解决这个问题?

Two entities are equal if they are of the same type and has the same primary key. 如果两个实体属于同一类型且具有相同的主键,则它们是相等的。

If you have integers for keys: 如果你有键的整数:

  1. Check for reference equality like you do now 像现在一样检查参考相等性
  2. If you have the Equal method in some base class you check that the types you're comparing are equal. 如果在某些基类中使用Equal方法,则检查您比较的类型是否相等。 Here you can get in to trouble with proxies, I'll return to that 在这里你可以遇到代理问题,我会回到那里
  3. Check if the primary keys are equal - that will not cause any lazy-loading 检查主键是否相等 - 这不会导致任何延迟加载

If you have GUIDs for keys: 如果您有密钥的GUID:

  1. Check for reference equality like you do now 像现在一样检查参考相等性
  2. Check if the primary keys are equal - that will not cause any lazy-loading 检查主键是否相等 - 这不会导致任何延迟加载

If I have integers for keys I usually have something like this Equal-override in a base class for my entities: 如果我有键的整数,我通常在我的实体的基类中有类似的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);
}

Now if you entities that inherit from others using table per hierarchy you will face the problem that GetClassWithoutInitializingProxy will return the base class of the hierarchy if it's a proxy and the more specific type if it's a loaded entity. 现在,如果您使用每个层次结构的表继承其他实体,您将面临如下问题:GetClassWithoutInitializingProxy将返回层次结构的基类(如果它是代理),如果它是加载的实体则返回更具体的类型。 In one project I got around that by traversing the hierarchy and thus always comparing the base types - proxy or not. 在一个项目中,我通过遍历层次结构来解决这个问题,因此总是比较基本类型 - 代理与否。

In these days though I would always go for using GUIDs as keys and do as described here: http://nhibernate.info/doc/patternsandpractices/identity-field-equality-and-hash-code.html 在这些日子里,虽然我总是会使用GUID作为键,并按照此处的描述进行操作: http//nhibernate.info/doc/patternsandpractices/identity-field-equality-and-hash-code.html

Then there is no proxy type mismatch problem. 然后没有代理类型不匹配问题。

If you are using identity equality, you should be able to access the key without triggering a load: 如果使用标识相等性,则应该能够在不触发加载的情况下访问密钥:

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

You can handle comparisons between objects before and after persisting by caching the hash code when it was transient. 您可以通过在哈希代码是瞬态时缓存哈希代码来处理持久化之前和之后的对象之间的比较。 This leaves a small gap in the Equals contract in that a comparison between an existing object that was transient will not generate the same hash code as a newly-retrieved version of the same object. 这在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;
    }
}

I use the following rules: 我使用以下规则:

  1. If entity has a POID property (remember that there is not need of property or any member just omit the name="XX", not sure if activerecord or the mapping strategy you are using supoprt this) 如果实体具有POID属性(请记住,不需要属性或任何成员只省略name =“XX”,不确定是否使用activerecord或您使用的映射策略supoprt this)

    • Not transient: If instance has ID != default(idType) then it is equals to another entity if both have the same id. 不是瞬态的:如果实例具有ID!= default(idType),那么如果它们具有相同的id,则它等于另一个实体。
    • Transient: If instance has ID == default(idType) then it is equals to another entity if both are the same Reference. 瞬态:如果实例具有ID == default(idType),则如果两者都是相同的Reference,则它等于另一个实体。 ReferenceEquals(this, other). ReferenceEquals(this,other)。
  2. If entity doesn't have a POID property , for sure you will need a natural-id. 如果实体没有POID属性 ,那么您肯定需要一个自然ID。 Use natural id for equality and GetHashCode. 使用自然id进行相等和GetHashCode。

  3. If you have a natural-id with many-to-one, instead of doing FooProperty.Equals(other.FooProperty), use FooProperty.Id.Equals(other.FooProperty.Id). 如果你有一个多对一的自然id,而不是做FooProperty.Equals(other.FooProperty),请使用FooProperty.Id.Equals(other.FooProperty.Id)。 Accessing the ID doesn't trigger the initialization of the lazy reference. 访问ID不会触发惰性引用的初始化。

Last but not least, using composite-id is discourage, and composite id with key-many-to-one is very discourage. 最后但并非最不重要的是,使用composite-id是不鼓励的,并且复合id与key-many-to-one非常不鼓励。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM