Ivan Krivyakov
Ivan Krivyakov

Reputation: 2038

How to implement GetHashCode for NHibernate entity with identity column?

I came to a conclusion that it is impossible to properly implement GetHashCode() for an NHibernate entity with an identity column. The only working solution I found is to return a constant. See below for explanation.

This, obviously, is terrible: all dictionary searches effectively become linear. Am I wrong? Is there a workaround I missed?

Explanation

Let's suppose we have an Order entity that refers to one or more Product entities like this:

class Product
{
    public virtual int Id { get; set; } // auto; assigned by the database upon insertion
    public virtual string Name { get; set; }
    public virtual Order Order { get; set; } // foreign key into the Orders table
}

"Id" is what is called an IDENTITY column in SQL Server terms: an integer key that is automatically generated by the database when the record is inserted.

Now, what options do I have for implementing Product.GetHashCode()? I can base it on

Each of these ideas does not work. If I base my hash code on Id, it will change when the object is inserted into a database. The following was experimentally shown to break, at least in the presence of NHibernate.SetForNet4:

/* add product to order */

var product = new Product { Name = "Sushi" }; // Id is zero
order.Products.Add(product); // GetHashCode() is calculated based on Id of zero
session.SaveOrUpdate(order); 

// product.Id is now changed to an automatically generated value from DB
// product.GetHashCode() value changes accordingly
// order.Products collection does not like it; it assumes GetHashCode() does not change

bool isAdded = order.Products.Contains(product); 

// isAdded is false; 
// the collection is looking up the product by its new hash code and not finding it

Basing GetHashCode() on the object identity (i.e. leaving Product with default implementation) does not work well either, it was covered on StackOverflow before. Basing GetHashCode() on Name is obviously not a good idea if Name is mutable.

So, what is left? The only thing that worked for me was

class Product
{
    ...
    override public GetHashCode() { return 42; }
}

Thanks for reading through this long quesiton. Do you have any ideas on how to make it better?

PS. Please keep in mind that this is an NHibernate question, not collections question. The collection type and the order of operations are not arbitrary. They are tied to the way NHibernate works. For instance, I cannot simply make Order.Products to be something like IList. It will have important implications such as requiring an index/order column, etc.

Upvotes: 2

Views: 503

Answers (1)

Vadim
Vadim

Reputation: 2865

I would base the hashcode (and equality, obviously) on the Id, that's the right thing to do. Your problem stems from the fact that you modify Id while the object is in the Dictionary. Objects should be immutable in terms of hashcode and equality while they are inside a dictionary or hashset.

You have two options -

  1. Don't populate dictionaries or hashsets before storing items in DB
  2. Before saving an object to the DB, remove it from the dictionaries. Save it to the DB and then add it again to the dictionary.

Update

The problem can also be solved by using others mappings

  1. You can use a bag mapping - it will be mapped to an IList and should work OK with you. No need to use HashSets or Dictionaries.
  2. If the DB schema is under your control, you may wish to consider adding an index column and making the relation ordered. This will again be mapped to an IList but will have a List mapping.

There are differences in performance, depending on your mappings and scenarios (see http://nhibernate.info/doc/nh/en/#performance-collections-mostefficientupdate)

Upvotes: 2

Related Questions