2

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

  • Id value
  • Name value
  • Identity of the product object (default behavior)

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.

4

1 回答 1

2

我会将哈希码(显然还有相等性)基于 Id,这是正确的做法。您的问题源于您在对象位于字典中时修改 Id 的事实。对象在字典或哈希集中时,在哈希码和相等性方面应该是不可变的。

你有两个选择——

  1. 在将项目存储到数据库之前不要填​​充字典或哈希集
  2. 在将对象保存到数据库之前,请将其从字典中删除。将其保存到数据库中,然后再次将其添加到字典中。

更新

该问题也可以通过使用其他映射来解决

  1. 您可以使用包映射 - 它将被映射到 IList 并且应该可以与您一起使用。无需使用 HashSet 或字典。
  2. 如果数据库模式在您的控制之下,您可能希望考虑添加一个索引列并使关系排序。这将再次映射到 IList,但将具有 List 映射。

性能存在差异,具体取决于您的映射和场景(请参阅http://nhibernate.info/doc/nh/en/#performance-collections-mostefficientupdate

于 2013-08-22T19:12:57.843 回答