How to add objects to association in OnPreInsert, OnPreUpdate

Posted by Dmitriy Nagirnyak on Stack Overflow See other posts from Stack Overflow or by Dmitriy Nagirnyak
Published on 2010-05-14T08:21:48Z Indexed on 2010/05/14 8:24 UTC
Read the original article Hit count: 376

Hi,

I have an event listener (for Audit Logs) which needs to append audit log entries to the association of the object:

public Company : IAuditable {
    // Other stuff removed for bravety
    IAuditLog IAuditable.CreateEntry() {
        var entry = new CompanyAudit();
        this.auditLogs.Add(entry);
        return entry;
    }
    public virtual IEnumerable<CompanyAudit> AuditLogs {
        get { return this.auditLogs }
    }
}

The AuditLogs collection is mapped with cascading:

public class CompanyMap : ClassMap<Company> {
    public CompanyMap() {
        // Id and others removed fro bravety
        HasMany(x => x.AuditLogs).AsSet()
            .LazyLoad()
            .Access.ReadOnlyPropertyThroughCamelCaseField()
            .Cascade.All();
    }
}

And the listener just asks the auditable object to create log entries so it can update them:

internal class AuditEventListener : IPreInsertEventListener, IPreUpdateEventListener {
    public bool OnPreUpdate(PreUpdateEvent ev) {
        var audit = ev.Entity as IAuditable;
        if (audit == null)
            return false;
        Log(audit);
        return false;
    }


    public bool OnPreInsert(PreInsertEvent ev) {
        var audit = ev.Entity as IAuditable;
        if (audit == null)
            return false;

        Log(audit);
        return false;
    }
    private static void LogProperty(IAuditable auditable) {
        var entry = auditable.CreateAuditEntry();            
        entry.CreatedAt = DateTime.Now;
        entry.Who = GetCurrentUser(); // Might potentially execute a query.
        // Also other information is set for entry here
    }
}

The problem with it though is that it throws TransientObjectException when commiting the transaction:

NHibernate.TransientObjectException : object references an unsaved transient instance - save the transient instance before flushing. Type: PropConnect.Model.UserAuditLog, Entity: PropConnect.Model.UserAuditLog
    at NHibernate.Engine.ForeignKeys.GetEntityIdentifierIfNotUnsaved(String entityName, Object entity, ISessionImplementor session)
    at NHibernate.Type.EntityType.GetIdentifier(Object value, ISessionImplementor session)
    at NHibernate.Type.ManyToOneType.NullSafeSet(IDbCommand st, Object value, Int32 index, Boolean[] settable, ISessionImplementor session)
    at NHibernate.Persister.Collection.AbstractCollectionPersister.WriteElement(IDbCommand st, Object elt, Int32 i, ISessionImplementor session)
    at NHibernate.Persister.Collection.AbstractCollectionPersister.PerformInsert(Object ownerId, IPersistentCollection collection, IExpectation expectation, Object entry, Int32 index, Boolean useBatch, Boolean callable, ISessionImplementor session)
    at NHibernate.Persister.Collection.AbstractCollectionPersister.Recreate(IPersistentCollection collection, Object id, ISessionImplementor session)
    at NHibernate.Action.CollectionRecreateAction.Execute()
    at NHibernate.Engine.ActionQueue.Execute(IExecutable executable)
    at NHibernate.Engine.ActionQueue.ExecuteActions(IList list)
    at NHibernate.Engine.ActionQueue.ExecuteActions()
    at NHibernate.Event.Default.AbstractFlushingEventListener.PerformExecutions(IEventSource session)
    at NHibernate.Event.Default.DefaultFlushEventListener.OnFlush(FlushEvent event)
    at NHibernate.Impl.SessionImpl.Flush()
    at NHibernate.Transaction.AdoTransaction.Commit()

As the cascading is set to All I expected NH to handle this. I also tried to modify the collection using state but pretty much the same happens.

So the question is what is the last chance to modify object's associations before it gets saved?

Thanks,
Dmitriy.

© Stack Overflow or respective owner

Related posts about nhibernate

Related posts about event-listeners