views:

124

answers:

4

I've overridden Equals and GetHashCode in an abstract base class to implement value equality based on an object's key property. My main purpose is to be able to use the Contains method on collections instead of Find or FirstOrDefault to check if an instance has already been added to a collection.

public abstract class Entity
{
    public abstract Guid Id { get; }

    public override bool Equals(object obj)
    {
        if (obj == null)
        {
            return false;
        }

        if (obj.GetType() != GetType())
        {
            return false;
        }

        var entity = (Entity)obj;
        return (entity.Id == Id);
    }

    public override int GetHashCode()
    {
        return Id.GetHashCode();
    }

}

The problem with this approach is that all my objects are equal before they've been persisted and acquired an Id (generated by NHibernate). Am I doing it wrong? I could generate the Id in the constructor, but I would like to implement the same pattern for other projects that use int ids, so that obviously won't work.

It seems to me that any object that overrides Equals will be equal to any other instance of the same object immediately after the objects have been instantiated.

Edited to add: Here's the scenario that concerns me: In the Add method for my collections, I am checking to make sure that the collection does not already contain the object to be added. If all new'ed up objects are equal, then I can never add two new objects to the collection.

+2  A: 

NHibernate gurantees that object reference identity of entities assocciated with the same context is the same as database identity. So there is no need to override Equals() and GetHashCode(), because this is the same identity you want to implement.

See 10.3. Considering object identity of the NHibernate Reference Documentation.

Further, assocciating a new entity using database generated keys with a context should immideatly persist the entity to the database and set the key of the entity to the generated key.

Daniel Brückner
Good point ... I don't think I will be keeping any collection references around between ISessions.
Jamie Ide
+1  A: 

You should obtain your ID, or at least some sort of identificator, in the constructor.

If the ID is not set (or is the default value); obviously the Equals method will return true for any object of the same type (that also has not set the ID).

driis
It wasn't obvious to me until I tried it, thanks.
Jamie Ide
A: 

May it help if you return "not equal" if the Id is not initialized?

public override bool Equals(object obj)
{
    if (obj == null)
    {
        return false;
    }

    if (obj.GetType() != GetType())
    {
        return false;
    }

    var entity = (Entity)obj;
    if ((Id == Guid.Empty) || (entity.Id == Guid.Empty))
    {
      return false;
    }
    return (entity.Id == Id);
}
rstevens
A: 

There's one major detail that affects your decision:

...I would like to implement the same pattern for other projects that use int ids...

Typically, IDs are generated in the database to enforce uniqueness. Now that you have GUIDs, you don't have to rely on the database to generate the value - GUIDs are guaranteed to be unique. Don't follow a pattern for the wrong reasons :)

So go ahead and create GUID values in the constructor - just be sure to have a setter so that NHibernate can overwrite the value when it fetches an entity from the database.

Additionally, you might want to consider using COMB GUIDs if your DBA is concerned GUIDs and database fragmentation.

Vijay Patel