I've wired up IPreInsertEventListener
and IPreUpdateEventListener
to manage audit logging in my app, which is using NHibernate as the ORM layer.
The problem I'm having is that the OnPreInsert()
event is fired when the object is persisted, but is not fired for each inserted child object as well.
In my case, I have a Canine
, which has a collection of CanineHandlers
. What's happening is:
- Create a new
Canine
- Create a new
CanineHandler
and add it to my newly createdCanine
- Save the
Canine
OnPreInsert
is fired with the newly createdCanine
and myLastUpdated
&LastUpdatedBy
fields are populated.- I get an error
not-null property references a null or transient value Model.CanineHandler.LastUpdatedBy
Obviously the problem is that the LastUpdated
fields aren't being filled in before NHibernate validates the CanineHandler
instance, but what I can't figure out is how to get NHibernate to call the OnPreInsert
method for each instance that's being persisted to the database.
One option that I've toyed with is adding another property to my Auditable
POCOs that contains a group of Auditable
child objects, but technically speaking I could save a CanineHandler
with a modified Canine
parent that has been changed, and then I'd be cascading the insert/update in the opposite direction.
So, am I on the right track with some incorrect code somewhere, or should I be approaching this a different way.
Here's the relevant (truncated) code:
Auditable interface
public interface IAuditable
{
DateTime LastUpdated { get; set; }
int? LastUpdatedBy { get; set; }
string AuditTypeName { get; }
Object AuditGetNew(String changes);
}
OnPreInsert event handler (implements IPreInsertEventListener interface)
public bool OnPreInsert(PreInsertEvent insertEvent)
{
if (insertEvent.Entity is IAuditable)
{
IAuditable auditablePoco = insertEvent.Entity as IAuditable;
auditablePoco.LastUpdated = DateTime.Now;
auditablePoco.LastUpdatedBy = (int?)PersonID;
//Taken directly from Ayende's blog, these calls update the
//event's state to match the entity's state
Set(insertEvent.Persister, insertEvent.State, "LastUpdated", auditablePoco.LastUpdated);
Set(insertEvent.Persister, insertEvent.State, "LastUpdatedBy", auditablePoco.LastUpdatedBy);
}
return false;
}
Canine mapping
<hibernate-mapping namespace="CanineApp.Model" assembly="CanineApp.Model" xmlns="urn:nhibernate-mapping-2.2">
<class name="Canine" table="Canine">
<id name="CanineID" type="Int32">
<generator class="identity" />
</id>
<property name="Name" type="String" length="64" not-null="true" />
<property name="LastUpdated" type="Date" />
<property name="LastUpdatedBy" type="Int32" />
<set name="CanineHandlers" table="CanineHandler" inverse="true"
order-by="EffectiveDate desc" cascade="save-update"
access="field.camelcase-underscore">
<key column="CanineID" />
<one-to-many class="CanineHandler" />
</set>
</class>
</hibernate-mapping>
CanineHandler mapping
<hibernate-mapping namespace="OPS.CanineApp.Model" assembly="OPS.CanineApp.Model" xmlns="urn:nhibernate-mapping-2.2">
<class name="CanineHandler" table="CanineHandler" schema="dbo">
<id name="CanineHandlerID" type="Int32">
<generator class="identity" />
</id>
<property name="HandlerPersonID" type="Int64" precision="19" not-null="true" />
<property name="EffectiveDate" type="DateTime" precision="16" not-null="true" />
<property name="LastUpdated" type="Date" not-null="true" />
<property name="LastUpdatedBy" type="Int32" not-null="true" />
<many-to-one name="Canine" class="Canine" column="CanineID" not-null="true" access="field.camelcase-underscore" />
</class>
</hibernate-mapping>