Lesser-Known NHibernate Features: Validation and Lifecycle Interfaces

NHibernate offers two interfaces that can be used to validate an entity before it is saved or updated or to cancel its saving, updating and deleting: IValidatable and ILifecycle. They are an alternative to events, and don’t need anything else to work other than be implemented by some entity.

Here’s what a possible implementation of IValidatable looks like:

   1: public class Product : IValidatable
   2: {
   3:     public String Name { get; set; }
   4:     public Decimal Price { get; set; }
   5:  
   6:     void IValidatable.Validate()
   7:     {   
   8:         if (String.IsNullOrWhitespace(this.Name) == true)
   9:         {
  10:             throw new InvalidOperationException("Name must be set");
  11:         }
  12:  
  13:         if (this.Price <= 0)
  14:         {
  15:             throw new InvalidOperationException("Price needs to be greater than 0");
  16:         }
  17:     }
  18: }

As you can see, you can cancel the current operation - save or update - by throwing an exception.

As for ILifecycle, it not only allows us to specifically cancel a save, update and also delete operation, but also to be notified as soon as the entity is loaded:

   1: public class Customer : ILifecycle
   2: {
   3:     public IList<Order> Orders { get; set; }
   4:     public Int32 Id { get; set; }
   5:     public String Name { get; set; }
   6:     
   7:     LifecycleVeto ILifecycle.OnDelete(ISession s)
   8:     {
   9:         return ((this.Orders.Any() == true) ? LifecycleVeto.Veto : LifecycleVeto.NoVeto);
  10:     }
  11:     void ILifecycle.OnLoad(ISession s, Object id)
  12:     {
  13:         Trace.WriteLine(String.Format("A customer with id {0} was loaded", id));
  14:     }
  15:     LifecycleVeto ILifecycle.OnSave(ISession s)
  16:     {
  17:         return ((String.IsNullOrWhitespace(this.Name) == true) ? LifecycleVeto.Veto : LifecycleVeto.NoVeto);
  18:     }
  19:     LifecycleVeto ILifecycle.OnUpdate(ISession s)
  20:     {
  21:         using (var childSession = s.GetSession(s.ActiveEntityMode))
  22:         {
  23:             var any = childSession.CreateQuery("select 1 from Customer where Id != :id and Name = :name").SetParameter("id", this.Id).SetParameter("name", this.Name).List<Int32>();
  24:             return ((any.Count != 0) ? LifecycleVeto.Veto : LifecycleVeto.NoVeto);
  25:         }
  26:     }

You even have access to the current session.

This is a classic feature of  NHibernate, but some people don’t like it because it “pollutes” our POCO entities with NHibernate-specific features, which makes our entities less reusable, and forces us to reference the NHibernate DLL. It may, however, come in handy sometimes as a "poor-man's" event system.

                             

3 Comments

  • The only caveat here, especially w/r/t IValidatable, is that you take a reference to NHibernate throughout your domain model. That's a pretty big price to pay for validation. The flip side is to roll your own validation in whatever repository you support, in such a way that the repository also does not need to know about the domain model, per se. Which ends up with a third, foundation assembly. Thoughts?

  • Looks like this was deprecated a year or so ago prior to you writing this, however. Still it is interesting. https://nhibernate.jira.com/browse/NH-879

  • Hi, Michael!
    This is not deprecated, that is, it is not marked as such (no ObsoleteAttribute) and it is working perfectly.
    As for the dependency on the NHibernate assembly, that's true, of course, but if you think of it, before version 4, you had to reference IESI Collections too. ;-)

Add a Comment

As it will appear on the website

Not displayed

Your website