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.