Kat Lim Ruiz
Kat Lim Ruiz

Reputation: 2562

NHibernate - listener not getting applied to cascaded child

I have the following problem with Nhibernate and a very simple parent-child relationship.

I have three listeners: Save, Update, Delete. If the object being persisted implements IAuditCreate interface, I assign the CreatedDate field.

My parent mapping

<hibernate-mapping assembly="Model" namespace="Model" xmlns="urn:nhibernate-mapping-2.2">
  <class name="SecSession" table="SEC_SESSION" lazy="true" >
    <id name="SecSessionId">
      <column name="SEC_SESSION_ID" sql-type="bigint" not-null="true" />
      <generator class="identity" />
    </id>
    <property name="CreatedDate">
      <column name="CREATED_DATE" sql-type="datetime" not-null="true" />
    </property>
    <bag name="SecSessionLogs" inverse="true" cascade="all-delete-orphan" >
      <key column="SEC_SESSION_ID" />
      <one-to-many class="SecSessionLog" />
    </bag>
  </class>
</hibernate-mapping>

My child mapping

<hibernate-mapping assembly="Model" namespace="Model" xmlns="urn:nhibernate-mapping-2.2">
  <class name="SecSessionLog" table="SEC_SESSION_LOG" lazy="true" >
    <id name="SecSessionLogId">
      <column name="SEC_SESSION_LOG_ID" sql-type="bigint" not-null="true" />
      <generator class="identity" />
    </id>
    <many-to-one lazy="false" name="SecSession">
      <column name="SEC_SESSION_ID" sql-type="bigint" not-null="true" />
    </many-to-one>
    <property name="LogMessage" type="StringClob">
      <column name="LOG_MESSAGE" sql-type="nvarchar(max)" not-null="true" />
    </property>
    <property name="CreatedDate">
      <column name="CREATED_DATE" sql-type="datetime" not-null="true" />
    </property>
  </class>
</hibernate-mapping>

So I create a SecSession object and then call SecSession.AddLog(new SecSessionLog).

To persist, I do:

using (var dataSession = DataStore.OpenDataSession())
  using (var transaction = dataSession.BeginTransaction())
  {
    var id = (PK)dataSession.Save(secSession);
    transaction.Commit();
    return id;
  }

I don't explicitly send the SecSessionLog to save, as the mapping says: CASCADE=ALL-DELETE-ORPHAN.

So the issue here is that the listener does not get called for the child Log object, so the CreatedDate field is empty and I get a null value exception in the database.

Is there something missing to configure in the mapping? in the listeners?

Your help will be much appreciated!

Thanks all

Upvotes: 1

Views: 1439

Answers (1)

Hailton
Hailton

Reputation: 1192

I also use cascading into an application. In my case I need to intercept the commands "insert", "delete" and "update" to ensure that only a specific subset of entities are being persisted within the scope of the operation performed.

In my case, the following events work: IPreInsertEventListener, IPreUpdateEventListener, IPreDeleteEventListener.

My previous answer is wrong!

Correct answer:

You can not use 'IPreInsertEventListener', at that time, change the properties of the entity has no effect. It happens that way because NHibernate has already captured the data that will use to run the SQL command. I have tested it.

I hope to have an answer soon.

But I think your problem should involve the following listeners: "merge", "save-update", "save", "update", "pre-collection-recreate", "pre-collection-remove" and "pre-collection-update".

My previous answer is wrong AGAIN!

I have tested and saw that using events listed just above * needs a lot of coding and I could not make it work with "merge" in "SecSession" followed by an insert and an update cascading into "SecSessionLogs".

There is a way to switch back to the NHibernate the property values after the modification within the listener.

*"Merge", "save-update", "save", "update", "pre-collection-recreate", "pre-collection-remove" and "pre-collection-update"

This code shows:

public class AuditCreateListener : IPreInsertEventListener, IPreUpdateEventListener
{
    public bool OnPreInsert(PreInsertEvent @event)
    {
        return this.OnEventCommon(
            @event, 
            new Action<object[]>(
                delegate(Object[] newState) 
                {
                    newState.CopyTo(@event.State, 0);
                }));
    }

    public bool OnPreUpdate(PreUpdateEvent @event)
    {
        return this.OnEventCommon(
            @event,
            new Action<object[]>(
                delegate(Object[] newState)
                {
                    newState.CopyTo(@event.State, 0);
                }));
    }

    private bool OnEventCommon(AbstractPreDatabaseOperationEvent @event, Action<object[]> setStateCallback)
    {
        IAuditCreate auditCreate = @event.Entity as IAuditCreate;
        if (auditCreate != null && auditCreate.CreatedDate == null)
        {
            auditCreate.CreatedDate = DateTime.Now;

            if (setStateCallback != null)
            {
                Object[] newState = @event.Persister.GetPropertyValues(auditCreate, EntityMode.Poco);
                setStateCallback(newState);
            }
        }

        return false;
    }
}

Soon I'll post the full source.

Edited Again:

Proof of Concept:

  • AuditCreateListenerBrokenTet: Test on the events: "save", "save-update", "update", "merge", "flush-entity", "pre-collection-recreate", "pre-collection-remove" and "pre-collection-update".
  • AuditCreateListenerTet: Test on the events: "pre-update" and "pre-insert".

Table contents:

SEC_SESSION:

#|SEC_SESSION_ID|CREATED_DATE    
-+--------------+----------------
1|1             |2012-07-10 16:53
2|2             |2012-07-11 16:53
3|3             |2012-07-12 16:53
4|4             |2012-07-13 16:53

SEC_SESSION_LOG:

#|SEC_SESSION_LOG_ID|LOG_MESSAGE  |CREATED_DATE    |SEC_SESSION_ID
-+------------------+-------------+----------------+--------------
1|1                 |LOG_MESSAGE_1|2012-07-10 16:53|1             
2|2                 |LOG_MESSAGE_2|2012-07-11 16:53|2             
3|3                 |LOG_MESSAGE_3|2012-07-12 16:53|3             
4|4                 |LOG_MESSAGE_4|2012-07-13 16:53|4             

I'm using:

  • NHibernate 3.2.0
  • System.Data.SQLite 1.0.80.0

The complete source is here: Q11495204.7z

NOTES:

  • Before opening the solution (".\ Src\SofPOC.2010.sln") run ".\Dependencies\setup.bat" to load dependencies.
  • See ".\readme.txt" and ".\dependencies\readme.txt" for instructions about the dependencies.

Upvotes: 2

Related Questions