Entity Life-Cycle in NHibernate: IInterceptor Interface

So what NHibernate provides you to manage entity state? At first sight all you need: interceptor concept via IInterceptor interface and persistent entity via ILifeCycle interface.

Let’s recall our needs. First of all, we want to validate entity before it saved into database and then we want to set some properties to entity (for example, last modified date, last editor, entity owner on first save and so on).

Interceptor

I’ll start from interceptor, it is bigger and more complex (good thing to start from, simple things in the end). OK, the first thing about interceptor is that you can have only one interceptor in ISession. It means you can’t create two interceptors with different responsibility in one session and should place all roles into one interceptor. Interceptor binds to session when it opened.

private void OpenSession()
{
 _session = _factory.OpenSession(new TriggerInterceptor());
 _session.FlushMode = FlushMode.Never;
}

IInterceptor has pretty much methods, here is brief explanation.

For example, when you add completely new object, workflow will be as on picture below:

Project project = new Project();
session.Save(project);
session.Flush();

OnSave. Called only when new entity saved into database (it means NHibernate thinks that entity is new, but you can trick NHibernate, it will be shown later).

IsUnsaved. Called only when new entity saved into database. In this method you can control whether entity will be updated or inserted, but in general this is not required.

PreFlush. Called each time before objects saved into database.

PostFlush. Called each time after objects saved into database. In this method you can execute some actions on saved objects. For example, invoke triggers. In general, if you want to support several databases, it is better to implement triggers in business logic layer.

Not surprisingly, when you change entity, workflow will be different.

project.Name = "new name";
session.Flush();

OnFlushDirty. Called when entity was changed (and NHibernate managed to check that) and saved into database. Pay attention that you can’t just set properties here, they will not be saved into database, so the following code will not save new ModifiedDate into database:

public bool OnFlushDirty(
 object entity, object id, object[] currentState,
 object[] previousState,  string[] propertyNames, IType[] types)
{
 (entity as EntityObject).ModifiedDate = DateTime.Now;
 return false;
}

Armed with such powerful knowledge, we can try to start custom entity life-cycle management implementation.

Validation

This is the simplest part. Each entity should validate itself, so it is naturally to have Validate() method in base class and implement custom validation rules for entities. Validate method will invoke all required assertions and throw exception if something will be incorrect. To validate entity, we should just call entity.Validate() in appropriate place. The main problem is to find such pleasant place.

In general, we’d like to know about entity validity as early as possible, at least somewhere before session flush. If we check pictures above, we have no problem for new entities. For example, we can put code into OnSave method

public bool OnSave(
 object entity, object id, object[] state,
 string[] propertyNames, IType[] types)
{
 (entity as EntityObject).Validate();
}

For changed entities we have fewer options. Unfortunately, there is no such method as OnUpdate in interceptor, all methods called when flush initiated. So we have to put validation into OnFlushDirty method.

public bool OnFlushDirty(
 object entity, object id, object[] currentState,
 object[] previousState,  string[] propertyNames, IType[] types)
{
 (entity as EntityObject).Validate();
 return false;
}

Custom OnSave Rules

So far so good. Let’s complicate things a bit. Very often you need to execute some rules before entity is saved. For example, we want to add logged user into project team when user creates new project. Another example, we want to mark user story as done when all it tasks marked as done. All these operations modify entity and that is the real problem.

Well, it looks simple. We can add ExecuteCustomOnSaveRules() method into EntityObject and override it in each entity.

public bool OnSave(
 object entity, object id, object[] state,
 string[] propertyNames, IType[] types)
{
 (entity as EntityObject).Validate();
 (entity as EntityObject). ExecuteCustomOnSaveRules ();
}

Then by analogy we can put method invocation in OnSave method, but we can’t invoke it in OnFlushDirty, as you already know, changes will not be applied. Are we stuck? Yes, we are. In next post I’ll bring ILifeCycle on board.

Additional Resources