Wednesday, 11 March 2009

Validation of NHibernate Entities

Recently I was reading Billy McCafferty's post "A Few NHibernate Tips" and one point there, related to mapping files, was particularly interesting for me:
Don’t bother including database meta data (e.g., column length) in mapping files unless intending to auto generate the SQL using the hbm2dll tool.
I was really surprised to see that things like column length, information if column accept null values are completely ignored.

Why does it matter?

There are actually two reasons:
  • if details regarding database meta data are irrelevant then why should we write mappings at all? Fluent Nhibernate and Auto Mapping is the way to go.
  • performance impact - it's a huge waste of time to make a call to database just to get an exception saying that some columns don't accept nulls. Luckily, there is a project called NHibernate Validator which can solve this issue.
Fluent NHibernate and NHibernate Validator in one project

It's fairly simple to configure those two to work together:

   1:  public static ISessionFactory GetFluentlyConfiguredSessionFactory()
   2:  {
   3:      return Fluently.Configure()
   4:          .Database(MsSqlConfiguration
   5:                        .MsSql2005
   6:                        .ConnectionString(c => c.Is(ConnectionString)))
   7:          .Mappings(m =>
   8:                    m.FluentMappings.AddFromAssemblyOf<Product>()
   9:                        .ConventionDiscovery.Add(new AdventureWorksConvention())
  10:                        .ExportTo(ExternalMappingsOutputDirectory))
  11:          .ExposeConfiguration(ConfigureNHibernateValidator)
  12:          .BuildSessionFactory();
  13:  }
  14:  
  15:  public static void ConfigureNHibernateValidator(Configuration configuration)
  16:  {
  17:      var nhvc = new NHVConfiguration();
  18:      nhvc.Properties[NHibernate.Validator.Cfg.Environment.ValidatorMode] = "UseAttribute";
  19:      nhvc.Mappings.Add(new NHibernate.Validator.Cfg.MappingConfiguration("AdventureWorksPlayground", null));
  20:      
  21:      var validator = new ValidatorEngine();
  22:      validator.Configure(nhvc);
  23:  
  24:      //Registering of Listeners and DDL-applying here
  25:      ValidatorInitializer.Initialize(configuration, validator);
  26:  }

First method - GetFluentlyConfiguredSessionFactory() is responsible for fluent-nh configuration. Second method, ConfigureNHibernateValidator(), instantiates Validator and registers listeners.
NHibernate Validator has two built-in NHibernate event listeners. Whenever a PreInsertEvent or PreUpdateEvent occurs, the listeners will verify all constraints of the entity instance and throw an exception if any of them are violated. Basically, objects will be checked before any insert and before any update triggered by NHibernate. This includes cascading changes! This is the most convenient and easiest way to activate the validation process. If a constraint is violated, the event will raise a runtime InvalidStateException which contains an array of InvalidValues describing each failure.
In your project you would like to change string "AdventureWorksPlayground", that is a name of your assembly. Probably you don't want to change validation mode which is set to "UseAttribute". Alternative is that you can use XML file for it.

Here is an example of domain object with a few validation rules:

   1:  public class SalesTerritory
   2:  {
   3:      public virtual int Id { get; set; }
   4:      
   5:      [NotNull]
   6:      [Pattern(Regex = "[A-Za-z0-9]+")]
   7:      public virtual string Name { get; set; }
   8:  
   9:      [NotNull]
  10:      public virtual string Group { get; set; }
  11:      
  12:      [Length(Max = 3), NotNull]
  13:      public virtual string CountryRegionCode { get; set; }
  14:  }

Out of the box you can use number different attributes to describe your validation rules, you can also create your own one if necessary. I was running a few tests and the results show that Validator will throw an exception four times faster then the NHibernate itself (and that was with database on the same machine).

You can learn much more about NHibernate Validator on NHibernate Forge wiki.

Shout it

1 comment:

Erik Burger said...

Something that really confuses me is that you both need to create your Fluent mappings AND add the Validator attributes..it would seem to me that there's a chance there for better integration.

That said, great post, very helpful in setting things up :)

Erik