Does this solve Nhibernate identity problem and GetHashCode issues?

后端 未结 3 2139
逝去的感伤
逝去的感伤 2021-01-22 03:22

The solution I propose involves quite a bit of code, but you can just copy it all and past it in a VS test solution assuming you have SqLite installed, and you should be able to

3条回答
  •  萌比男神i
    2021-01-22 03:56

    That's an interesting approach but instead of taking the time to understand and critique I will just offer my solution to this problem.

    I don't like the idea of a generic entity base class, so my solution only supports int, Guid and string identities. Some of the code below, such as using a Func to get the hash code, only exists to support case-insensitive string comparisons. If I ignored string identifiers (and I wish I could), the code would be more compact.

    This code passes the unit tests I have for it and hasn't let me down in our applications but I'm sure there are edge cases. The only one I've thought of is: If I new and save an entity it will keep its original hash code, but if after the save I retrieve an instance of the same entity from the database in another session it will have a different hash code.

    Feedback welcome.

    Base class:

    [Serializable]
    public abstract class Entity
    {
        protected int? _cachedHashCode;
    
        public abstract bool IsTransient { get; }
    
        // Check equality by comparing transient state or id.
        protected bool EntityEquals(Entity other, Func idEquals)
        {
            if (other == null)
            {
                return false;
            }
            if (IsTransient ^ other.IsTransient)
            {
                return false;
            }
            if (IsTransient && other.IsTransient)
            {
                return ReferenceEquals(this, other);
            }
            return idEquals.Invoke();
        }
    
        // Use cached hash code to ensure that hash code does not change when id is assigned.
        protected int GetHashCode(Func idHashCode)
        {
            if (!_cachedHashCode.HasValue)
            {
                _cachedHashCode = IsTransient ? base.GetHashCode() : idHashCode.Invoke();
            }
            return _cachedHashCode.Value;
        }
    }
    

    int identity:

    [Serializable]
    public abstract class EntityIdentifiedByInt : Entity
    {
        public abstract int Id { get; }
    
        public override bool IsTransient
        {
            get { return Id == 0; }
        }
    
        public override bool Equals(object obj)
        {
            if (obj == null || obj.GetType() != GetType())
            {
                return false;
            }
            var other = (EntityIdentifiedByInt)obj;
            return Equals(other);
        }
    
        public virtual bool Equals(EntityIdentifiedByInt other)
        {
            return EntityEquals(other, () => Id == other.Id);
        }
    
        public override int GetHashCode()
        {
            return GetHashCode(() => Id);
        }
    }
    

    Guid identity:

    [Serializable]
    public abstract class EntityIdentifiedByGuid : Entity
    {
        public abstract Guid Id { get; }
    
        public override bool IsTransient
        {
            get { return Id == Guid.Empty; }
        }
    
        public override bool Equals(object obj)
        {
            if (obj == null || obj.GetType() != GetType())
            {
                return false;
            }
            var other = (EntityIdentifiedByGuid)obj;
            return Equals(other);
        }
    
        public virtual bool Equals(EntityIdentifiedByGuid other)
        {
            return EntityEquals(other, () => Id == other.Id);
        }
    
        public override int GetHashCode()
        {
            return GetHashCode(() => Id.GetHashCode());
        }
    }
    

    string identity:

    [Serializable]
    public abstract class EntityIdentifiedByString : Entity
    {
        public abstract string Id { get; }
    
        public override bool IsTransient
        {
            get { return Id == null; }
        }
    
        public override bool Equals(object obj)
        {
            if (obj == null || obj.GetType() != GetType())
            {
                return false;
            }
            var other = (EntityIdentifiedByString)obj;
            return Equals(other);
        }
    
        public virtual bool Equals(EntityIdentifiedByString other)
        {
            Func idEquals = () => string.Equals(Id, other.Id, StringComparison.OrdinalIgnoreCase);
            return EntityEquals(other, idEquals);
        }
    
        public override int GetHashCode()
        {
            return GetHashCode(() => Id.ToUpperInvariant().GetHashCode());
        }
    }
    

提交回复
热议问题