An improperly mapped enumeration can lead to unnecessary updates. In this recipe, we'll discuss why and show you how to map an enumeration property to a string field.
Enumerations
to the MappingRecipes
project.AccountTypes
enum to the folder:public enum AccountTypes { Consumer, Business, Corporate, NonProfit }
Account
class:public class Account { public virtual Guid Id { get; set; } public virtual AccountTypes AcctType { get; set; } public virtual string Number { get; set; } public virtual string Name { get; set; } }
Account.hbm.xml
with the following class mapping:<class name="Account">
<id name="Id">
<generator class="guid.comb" />
</id>
<natural-id>
<property name="Number" not-null="true" />
</natural-id>
<property name="Name" not-null="true" />
<property name="AcctType" not-null="true" />
</class>
property
element for AcctType
, add a type
attribute with the following value:NHibernate.Type.EnumStringType`1[[MappingRecipes.Enumerations.AccountTypes, MappingRecipes]], NHibernate
Recipe
class:using System; using NH4CookbookHelpers.Mapping; using NHibernate; namespace MappingRecipes.Enumerations { public class Recipe : HbmMappingRecipe { protected override void AddInitialData(ISession session) { session.Save(new Account { Name = "Test account", Number = "1", AcctType = AccountTypes.Consumer }); } public override void RunQueries(ISession session) { var accounts=session.QueryOver<Account>() .OrderBy(x=>x.Name).Asc .List(); foreach (var account in accounts) { Console.WriteLine("Account name: {0},type: {1}",account.Name,account.AcctType); } } } }
By default, NHibernate will map an enumeration to a numeric field based on the enumeration's underlying type, typically an int. For example, if we set AcctType
to AccountTypes.Corporate
, the AcctType
database field would hold the integer 2
. This has two significant drawbacks. An integer value by itself doesn't describe the business meaning of the data. Also, if the order of the enum members change and they don't have assigned values, the wrong AccountTypes
value will be returned for previously stored entities.
One solution, shown here, is to store the name of the enumeration value in a string field. For example, if we set AcctType
to AccountTypes.Corporate
, the AcctType
database field would hold the string value Corporate
.
By specifying a type
attribute for AcctType
, we tell NHibernate to use a custom class for conversion between .NET types and the database. NHibernate includes EnumStringType<T>
to override the conversion of enumeration values to database values so that the string name is stored, not the numeric value.
The type
value: NHibenate.Type.EnumStringType`1[[MappingRecipes.Enumerations.AccountTypes, MappingRecipes]], NHibernate
is the assembly qualified name for NHibernate.Type.EnumStringType<AccountTypes>
.
In the introduction to this recipe, we mentioned that an improperly mapped enum can lead to unnecessary updates. What did we mean by that? Let's try to introduce that error!
not-null="true"
attribute on AcctType
.Recipe
class' AddInitialData
method, add this code to force an Account
into the database via plain SQL:session.CreateSQLQuery( @"INSERT INTO Account (Id,Name,Number) VAlUES(:id,:name,:number)") .SetGuid("id", Guid.NewGuid()) .SetString("name", "Test account 2") .SetString("number", "2") .ExecuteUpdate();
If things worked as expected, will we see an UPDATE
query after the SELECT? Why on earth?
When we force inserted the Account
via SQL, we omitted the AcctType
column, resulting in a NULL
value in that column. The query in RunQueries
then loads this data from the database and maps it into new Account
instances. Since the AcctType
property is an enum, it tried to map the NULL
into a valid enum member, which is not possible. Instead, it falls back to the first enum member, in this case Consumer
. When the session is finally flushed (caused by the transaction commit), NHibernate sees that the persistable data for the Account
instance is different from the way it looked when the data was loaded. The entity is classified as being dirty and NHibernate accordingly issues an UPDATE
.
This problem is not unique to enums. It can happen any time you use a value type (int
, decimal
, Guid
, and so on.) property and the corresponding database column allows NULL
values. There are two solutions to this problem:
int?
or AccountTypes?
NULL
values.18.227.26.217