Complex NHibernate Auditing

谁说我不能喝 提交于 2019-12-04 12:07:13

I have a requirement similar to yours. In my case, it's a health care application and the audit log needs to identify the patient to which the insert/update applies.

My solution is to define an interface, which all audited classes need to implement :

public interface IAuditedRecord
{
    IPatient OwningPatient { get; }

    ...
    // Other audit-related properties (user, timestamp)
}

The audited classes then implement this interface in whatever way is required. For example:

public class Medication : IAuditedRecord
{
    // One end of a bidirectional association. Populated by NHibernate.
    private IPatient _patient;

    IPatient OwningPatient { get { return _patient; } }
}

public class MedicationNote : IAuditedRecord
{
    // One end of a bidirectional association. Populated by NHibernate.
    private Medication _medication;

    IPatient OwningPatient { get { return _medication.OwningPatient; } }
}

The IPostInsertEventListener and IPostUpdateEventListener then fetch the OwningPatient property in order to populate the audit record.

The solution has the advantages of keeping the auditing logic in the event listeners, which is the only place where one is sure that an insert/update will take place, as well as allowing the traversal of indirect links between an object and its owning patient.

The downside is that the audited classes have to derive from a specific interface. I think the benefits outweigh this small cost.

This seems like a more complicated scenario than a trivial audit trail, and for that reason I would lean towards an application service to handle this.

  1. Its much more testable.
  2. Its explicit.
  3. You don't have to resort to reflection or common properties.

We use an audit service in our application, even though our scenario is much simpler than yours because its a domain concern. Our domain knows about history and works with history, its not created just for some reporting front end.

Are you recording who (Employee) is making the update, or is Employee some sort of aggregate root?

For #1, I designed the following: In MyCommon assembly I declared

public interface IUserSessionStore
{
    UserSession Get();
    void Set(UserSession userSession);
}

which gets created per request using Ninject. The implementation of this class, simply stores the UserSession object in the ASPNET MVC Session.

With this, I can request the IUserSessionStore throughout all layers and get the UserSession for this class to use and to insert as audit info in every object (below class would be in MyModel project):

public class AuditUpdater : DefaultSaveOrUpdateEventListener
{
    private IUserSessionStore userSessionStore;
    public AuditUpdater(IUserSessionStore userSessionStore)
    {
        this.userSessionStore = userSessionStore;
    }

    private Guid GetUserId()
    {
        return userSessionStore.Get().UserId;
    }

    private void UpdateAuditCreate(IAuditCreate auditable)
    {
        if (auditable != null)
        {
            auditable.CreationDate = DateTime.UtcNow;
            auditable.CreatedBy = GetUserId();
        }
    }

    .......
}

So you can adapt this to obtain your Employee information that you need.

Gladly I'd take more suggestions!

To create a log you can use IPostInsertEventListener or IPostUpdateEventListener. If you are using fluent configuration you has that configuring as in the example bellow.

The events will be called when have a post or update commit.

.ExposeConfiguration(c => c.EventListeners.PostCommitInsertEventListeners = new IPostInsertEventListener[] { new AuditEventPostInsert() })
.ExposeConfiguration(c => c.EventListeners.PostCommitUpdateEventListeners = new IPostUpdateEventListener[] { new AuditEventPostUpdate() });
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!