Abstract
Audit trails using NHibernate's event model often
use the OnPreInsert and OnPreUpdate event listeners to change/ modify the state
of the entity. Although It may work in some cases and
is sometimes referred to as a solution, it should be noted the OnPreInsert and
OnPreUpdate events are not intended to be used to change the values of the
entity and instead they should be used to check values and for that reason they
return "veto"
There is also a bug report about that.
Why OnPreUpdate Triggers May Fail
There is a reason for that. NHibernate does the folowing:
- When flushing, the FlushEntityEventListener is called for each entity in the session.
- It determines the dirty properties.
- Based on the dirty properties, it determines if the entity needs updating
- It calls PreUpdate triggers
- Based on the dirty properties it updates the corresponding tables.
The last point is the critical one. If your PreUpdate trigger changes properties that are not found dirty before and which are not in the same table as the dirty properties, it won't be updated in the database. The same happens when dynamic-update is used.
Example:
<class name="Foo">
<id ...>
<property name="Name"/>
<join table="AnotherTable>
<key name="id"/>
<property name="Value"/>
</join>
</class>
Now, when you change Name in the session (it gets dirty) and Value in the trigger, the table AnotherTable won't be updated.
How To Implement It The Correct Way
The correct way is to use the FlushEntityEventListener to implement property changes.The important difference is that you can provide a list of changed properties in this event listener, and therefore tell NHibernate what to store.
There is an example by Buthrakaur.
History
There is a solution to reproduce the code by Scott Findlater here. The tests
folder in each project highlights successes/ issues (by failing tests) in each
project.
Project - 01OnPreEvents. This project shows a typical, working,
approach to audit trails using the OnPreInsert and OnPreUpdate to modify the
entity state. An Entity base class which is inherited by a Category class. The base Entity provides identification and
common auditing properties.
Project - 02OnPreEventsFailing. This project shows how an inheritance entity
hierarchy using the OnPreInsert and OnPreUpdate events to modify entity state
fail to persist the changed entity state.
Here, for whatever design decision, the Entity base class which originally provide identification and
common auditing has been split into Entity
and AuditableEntity. Now the Category
class inherits from AuditableEntity.
Project - 03Save. Uses the same entity inheritance hierarchy
from the 02OnPreEventsFailing project
to demonstrate a working version using the Save/ Insert events.
References to the problem
Audit trails using NHibernate's event model are well
documented;
There is also this
NHibernate bug report - changes made in IPreInsert/UpdateEventListener are not
persisted into DB on inherited classes with the resolution of "not an issue" because "pre-insert
and pre-update listeners are not intended to be used to change the values of
the entity. Instead they should be used to check-values (for that reason they
return "veto")." (see
in context)
This contradiction of intended usage regarding the
changing of entity state was further discussed on this NHUser
Group thread, with the same answer.
There are some discussions and blog posts related to this problem: