views:

27

answers:

1

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:

  1. Create a new Canine
  2. Create a new CanineHandler and add it to my newly created Canine
  3. Save the Canine
  4. OnPreInsert is fired with the newly created Canine and my LastUpdated & LastUpdatedBy fields are populated.
  5. 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>
+1  A: 

Not sure this will work, but I noticed that your Canine mappings for LastUpdatedBy and LastUpdated are not the same as for CanineHandler. Perhaps try removing the not-null="true" from CanineHandler mappings.

I have done auditing for nHibernate in the same fashion and it worked fine, although I used the older intercepting interface, namely IInterceptor. Also I did not have not-null="true" in my mappings.

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"  />
    <property name="LastUpdatedBy" type="Int32"  />
    <many-to-one name="Canine" class="Canine" column="CanineID" not-null="true" access="field.camelcase-underscore" />
  </class>
</hibernate-mapping>
Iain
This works. I'd still love to see the event model spelled out somewhere, since this problem seems to have come from a mis-understanding on my part of when the OnPreInsert event fires in the pipeline.
Kendrick