This is part of a series of posts about NHibernate Pitfalls. See the entire collection here.
When you have properties that are of a value type (typically Byte, SByte, Boolean, Char, Int16, UInt16, Int32, UInt32, Int64, UInt64, Decimal, Single, Double, DateTime, TimeSpan, Guid or Enum-derived), you must take the following precautions:
- If the associated column on the database allows NULL values, the property must be nullable;
- If the property is of an enumerated type, you must choose whether you want it persisted as a string of characters (typically a VARCHAR or its UNICODE counterpart) or as an integer (an INT on most databases).
Why is that? Imagine that you map a nullable INT column containing NULL to a not-nullable Int32 property, what would happen? Remember, in .NET, null is not the same as 0! When a session goes to check if the session is dirty (meaning that the entity must be saved to the database), it sees that the value initially loaded from the database (NULL) is not the same as the current value (0), and so it decides that the entity is dirty, resulting in an unnecessary UPDATE. As for enumerated types, more or less the same thing happens: databases (well, most of, anyway) don’t know nothing about enumerated values, but they know VARCHAR and INT, so, when the session is dirty checked, NHibernate sees that the current value of the property (the enumeration) is not the same as the loaded value, and decides it is time for an UPDATE too. These non-existing changes are usually called ghosts and are sometimes hard to find.
So, what you must do is:
- Declare the property as “Int32 ?” instead of just “Int32” (or “MyEnum ?” instead of “MyEnum”) if the underlying column is nullable;
- In the mapping for the enumerated type property, declare the appropriate NHibernate type for dealing with the enumerated value: EnumStringType<T> for enumerations that are to be persisted as strings of characters, EnumCharType<T> if it is to be stored as a single character or PersistentEnumType if it is to be stored as an integer.