开发者

DDD: GetHashCode and primary id

I have seen DDD Domain implementations where entities rely on the primary key ID when building Equals/GetHashCode methods. I understand why it is a good idea, as the primary key might be the only member which is not mutable. But there might also be situations where it is no good idea, though.

Think about a Dictionary, holding entities whi开发者_C百科ch where just instantiated. The primary key (assume it is an auto-incrementing value, not a "business-related" value) is not assigned yet, it might be 0 on each entity. Now, the entities within the Dictionary will be saved. This implies a change of the primary key resulting in a different hash code.

My question: what pattern should be used regarding GetHashCode if no business-related primary key is available and all members are mutable?

Thank you in advance.


In such cases, I only rely on the Id in the Equals and GetHashcode methods, if the Id has been assigned. In other cases, I compare for equality using the reference. The same goes for the GetHashCode implementation; I only create a hashcode based on the 'Id', if the Id has been assigned. (That is, if the entity is not transient).

The code below, is what I use. This is somewhat based on the entities in Sharp architecture:

        public override bool Equals( object obj )
        {
            Entity<TId> other = obj as Entity<TId>;

            if( other == null || this.GetType() != other.GetType() )
            {
                return false;
            }

            bool otherIsTransient = Equals (other.Id, default(TId));
            bool thisIsTransient = Equals (this.Id, default (TId));

            if( otherIsTransient && thisIsTransient )
            {
                return ReferenceEquals (this, other);
            }

            return Id.Equals (other.Id);

        }

First, I check whether both entities are of the same type. (Actually, you could create a typed version of this method as well, by implementing the correct interface (IEquality<T>) When both entities are of the same type, I check whether one of them is transient. I just do it by checking their Id property: if it contains the default value, it means that they haven't been saved in the DB yet. If both of them are transient, I compare both entities on their reference. Otherwise, I can compare them on their ID.

The GetHashCode method that I use, makes sure that the value that's being returned, never changes:

        private int? _oldHashCode;

        public override int GetHashCode()
        {
            // Once we have a hash code we'll never change it
            if( _oldHashCode.HasValue )
            {
                return _oldHashCode.Value;
            }

            bool thisIsTransient = Equals (Id, default(TId));


            // When this instance is transient, we use the base GetHashCode()
            // and remember it, so an instance can NEVER change its hash code.

            if( thisIsTransient )
            {
                _oldHashCode = base.GetHashCode ();

                return _oldHashCode.Value;
            }

            return Id.GetHashCode ();
        }


You can check, assuming it's an auto-incremented identity, and only compare on Id if they are not 0 or default(int). Something like:

public virtual bool Equals(MyClass other)
        {
            if (ReferenceEquals(null, other)) return false;
            if (ReferenceEquals(this, other)) return true;
            if (Id != default(int) && other.Id != default(int))
                return Equals(Id, other.Id);
            return Equals(other.ChildProp, ChildProp);
        }


Well, if you can't tell that an object equals another you should not do it :)

It is ok if equals returns false all the time (given another object than itself). If different local objects (without id) get different ids at the time inserted into the db/storage this is perfectly sane.

If you can tell that two objects are equal and they would both be mapped into the same table line and thus would get the same id on store Equals should return true, intuitively. But you are right that using mutable values to determine equality (and esp. hash code) can be dangerous. Collections usually get confused when you do this. In this case it is probably better to use other methods than Equals and GetHashCode to implement this kind of equality, as it really changes over time.

So essentially you need to determine which kind of equality you really need for what purpose.


I expect that the systems you have seen use NHibernate for ORM. The guidelines for NHibernate (which supposedly allows you entities to be POCOs, and enforces no requirement on them) recommends this practice. The problem is that NH sometimes has difficulty recognising an entity as being the same as another instance, especially if the session has been closed and reopened, or if two proxies represent the same object. You can end up with the same entity twice in a collection if you do not do this. Follow this link for more information:

http://community.jboss.org/wiki/EqualsAndHashCode

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜