Добавить объекты к ассоциации в OnPreInsert, OnPreUpdate - PullRequest
3 голосов
/ 14 мая 2010

У меня есть прослушиватель событий (для журналов аудита), которому необходимо добавить записи журнала аудита к ассоциации объекта:

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 }
    }
}

Коллекция AuditLogs сопоставляется с каскадным :

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

И слушатель просто просит проверяемый объект создать записи журнала, чтобы он мог их обновить:

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 Log(IAuditable auditable) {
        var entry = auditable.CreateAuditEntry();  // Doing this for every auditable property
        entry.CreatedAt = DateTime.Now;
        entry.Who = GetCurrentUser(); // Might potentially execute a query as it links current user with log entry
        // Also other information is set for entry here
    }
}

Однако проблема в том, что при совершении транзакции он выдает TransientObjectException:

NHibernate.TransientObjectException : object references an unsaved transient instance - save the transient instance before flushing. Type: CompanyAudit, Entity: CompanyAudit
    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()

Поскольку для каскада установлено значение Все Я ожидал, что NH справится с этим. Я также попытался изменить коллекцию, используя state, но примерно так же происходит.

Таким образом, вопрос каков последний шанс изменить ассоциации объекта перед его сохранением ?

Спасибо
Дмитрий.

Ответы [ 2 ]

3 голосов
/ 28 октября 2010

http://ayende.com/Blog/archive/2009/04/29/nhibernate-ipreupdateeventlistener-amp-ipreinserteventlistener.aspx

Короче говоря, похоже, что при запуске OnPreInsert NHibernate уже определил, что нужно обновить.Если после этого что-то становится грязным, вам необходимо дополнительно обновить «состояние объекта», запись для объекта в списке «грязных» объектов.

Ваша реализация IAuditable в Company вносит изменения в этот объект;а именно, добавление нового объекта в коллекцию.В случае создания совершенно новых объектов, лучшая практика (как упомянул Ревин Харт в комментариях к записи в блоге), по-видимому, заключается в создании дочерней сессии и сохранении там нового объекта.Это звучит гораздо проще, чем добавлять все необходимые записи в состояние объекта с помощью вызовов Set ().Попробуйте извлечь IAuditLog из вызова IAuditable.CreateEntry () и сохранить его, используя код, подобный следующему:

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

    var log = Log(audit);

    ISession newSession = ev.Source.PersistenceContext.Session.GetSession();
    newSession.Save(log);
    return false;
}


private static IAuditLog Log(IAuditable auditable) {
    var entry = auditable.CreateAuditEntry();  // Doing this for every auditable property
    entry.CreatedAt = DateTime.Now;
    entry.Who = GetCurrentUser(); // Might potentially execute a query as it links current user with log entry

    return entry;
}
0 голосов
/ 15 ноября 2010

Я очень старался использовать прослушиватели событий NH с другой проблемой.
Поэтому я просто решил использовать Interceptor (на основе EmptyInterceptor).

Мне нужно только переопределить SetSession, OnFlushDirty, OnSave методы и подключиться к ним.

Они позволяют создавать правильные объекты и сохранять их.

Так что это самое жизнеспособное решение на данный момент.

...