Mapping enumerations

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.

How to do it…

  1. Add a new folder named Enumerations to the MappingRecipes project.
  2. Add the following AccountTypes enum to the folder:
    public enum AccountTypes
    {
      Consumer,
      Business,
      Corporate,
      NonProfit
    }
  3. Add the following 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; }
    }
  4. Add an embedded NHibernate mapping document named 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>
  5. On the property element for AcctType, add a type attribute with the following value:
    NHibernate.Type.EnumStringType`1[[MappingRecipes.Enumerations.AccountTypes, MappingRecipes]], NHibernate
  6. Add the following 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);
          }
        }
      }
    }

How it works…

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>.

Unnecessary updates

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!

  1. In the mapping document, remove the not-null="true" attribute on AcctType.
  2. In the 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();
  3. Start the recipe again and take note of the query log.

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:

  • Make the property nullable, for example using int? or AccountTypes?
  • Modify the database to disallow NULL values.

See also

  • Using the Ghostbusters test
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.223.171.168