Lazy-loaded NHibernate properties in Equals and GetHashCode

后端 未结 3 1448
夕颜
夕颜 2021-01-03 03:49

How can the following problem be dealt with?

We\'re using lazy loaded NHibernate properties and whenever we\'re calling Equals() or GetHashCode()<

相关标签:
3条回答
  • 2021-01-03 04:21

    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.

    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;
        }
    }
    
    0 讨论(0)
  • 2021-01-03 04:34

    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. 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:

    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:

    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. 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

    Then there is no proxy type mismatch problem.

    0 讨论(0)
  • 2021-01-03 04:42

    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)

      • Not transient: If instance has ID != default(idType) then it is equals to another entity if both have the same id.
      • Transient: If instance has ID == default(idType) then it is equals to another entity if both are the same Reference. ReferenceEquals(this, other).
    2. If entity doesn't have a POID property, for sure you will need a natural-id. Use natural id for equality and 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). Accessing the ID doesn't trigger the initialization of the lazy reference.

    Last but not least, using composite-id is discourage, and composite id with key-many-to-one is very discourage.

    0 讨论(0)
提交回复
热议问题