Tuesday, January 24, 2006

So I have been applying NHibernate.Generics to all my projects. I have learned some valuable lessons.

NHibernate has always warned that all entity classes should implement their own Equals and GetHashCode methods. I've procrastinated implementing them because NHibernate seemed to work just fine without them. Then I started using proxy classes for lazy loading, and problems started popping up with objects comparing themselves against each other and deciding they were not equal when in fact they represented the same entity. Lately while applying the new NHibernate.Generics collections, I found that if I had a collection of two entities, there would be three entities in the collection, where one of the entities was in there twice! My first reaction was "Ugh! This collection class is buggy!" Then I remembered my neglected absence of GetHashCode and Equals. Without them, how is a collection to know that two objects (where one is only a proxy class) are actually the same entity?

So here is the code I hurriedly injected into every last one of my entity classes:

With these two methods, my problems disappeared! I even went so far as to write a reusable Code Snippet that would assist me in setting the class name that must appear in each segment. I include the snippet file here:

xmlversion="1.0"encoding="utf-8" ?><CodeSnippetsxmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet"><CodeSnippetFormat="1.0.0"><Header><Title>Entity Equals and GetHashCodeTitle><Shortcut>nheqShortcut><Author>Andrew ArnottAuthor><SnippetTypes><SnippetType>ExpansionSnippetType>SnippetTypes><Description>Inserts the Equals and GetHashCode methods that NHibernate requires to run correctly.Description>Header><Snippet><CodeLanguage="CSharp">[CDATA[ /// <summary> /// Tests whether this and another object are equal in a way that /// will still pass when proxy objects are being used. /// summary> public override bool Equals(object obj) { $type$ other = obj as $type$; if (other == null) return false; if (Id == 0 && other.Id == 0) return (object)this == other; else return Id == other.Id; }

1 comment:

I think you need to be careful about using Id (database identity) in your GetHashCode() function. If you have two transient (unsaved) objects in a collection, their IDs default and are unset (until NH persists them). If IDs are DB identities, then GetHashCode() would generate the same hash code for two different (unsaved) transient objects of the same type.

It looks like Equals() will take care of this ambiguity, but it would lead to a lot of duplicated code in your domain objects.