NHibernate Comprehensive Audit

I use the IPostUpdateEventListener interface to now update the audit log, capture old and new values, and then save each updated field in the Audit table and all that jive. The works swell, but there are two last requirements that I have difficulty filling out:

  • Show on which employee the update was.
  • Display the "friendly" name of the updated field.

For # 1, my first instinct was to use reflection, and look for and capture the “Employee” property on this object to find out which employee it was for, but it quickly falls apart when you are in several objects on the chart without automatic a way to return to this Employee object.

Ideas for solution # 1 ranged from the requirement for the Parent property on each object, so I can cross the graph looking for the Employee type (which, for me, would pollute our domain too much for a simple concern) is a separate SQL job for converting foriegn keys and filling in the Employee identifier after the fact (I would prefer not to support a separate SQL task, since everything that is actually the code based at this stage), and this SQL operation will be rather unpleasant very fast).

As for the second requirement, I can get the actual property name, which has changed just fine. For the good 80% - 90% of our fields, the property name (properly formatted) is what we show, so I can just specify a name based on the Pascal case. The remaining fields, however, do not coincide for various reasons. We use ASP.NET MVC and Fluent HTML constructors from MvcContrib, but even if we change the setting to such an extent that we have an attribute in the view model that reevaluates what should be the name of the field (and therefore have it in the code instead just a view), there is no real way to map these attributes from view models to persisted domain objects.

The final pragmatic solution to both problems would be to call the audit service for logging after each update operation in another service, passing in the field names and employee information as needed, but, well, I really don't want to go there for obvious reasons .

Ideas for any problem would be very helpful. Finding and breaking my brain for a couple of days didn’t bring anything useful - most people seem to stop at a simple recording of old or new records or just a “created / updated” time stamp on the record itself.

+4
source share
4 answers

I have a requirement similar to yours. In my case, this healthcare application and audit trail should identify the patient to whom the insert / update applies.

My solution is to define the interface that all classes to be tested must implement:

 public interface IAuditedRecord { IPatient OwningPatient { get; } ... // Other audit-related properties (user, timestamp) } 

Validated classes then implement this interface in any way. 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; } } } 

Then, IPostInsertEventListener and IPostUpdateEventListener extract the IPostUpdateEventListener property to populate the audit record.

The solution has the advantages of maintaining the audit logic in event listeners, which is the only place where you can make sure that the insert / update will take place, as well as allow circumvention of indirect connections between the object and its owning patient.

The disadvantage is that the tested classes must be based on a specific interface. I think the benefits outweigh this small cost.

+3
source

This seems like a more complex scenario than a trivial audit trail, and for this reason I am leaning towards an application service to handle this.

  • Its much more verifiable.
  • Explicit.
  • You do not need to resort to reflection or general properties.

We use the audit service in our application, although our script is much simpler than yours because it concerns the domain. Our domain knows about history and works with history, and is not created just for some front-end reporting.

Do you write down who (the employee) is doing the update, or is Employee some aggregate root?

0
source

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

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

which is created on demand using Ninject. An implementation of this class simply stores the UserSession object in an ASPNET MVC session.

With this, I can request an IUserSessionStore at all levels and get a UserSession for this class and insert as audit information in each object (the class will be in the MyModel project below):

 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 get the necessary information from your employee.

With pleasure I will make more offers!

0
source

To create a log, you can use IPostInsertEventListener or IPostUpdateEventListener. If you use a free configuration, you have a setting like in the following example.

Events will be triggered when a message or update is committed.

 .ExposeConfiguration(c => c.EventListeners.PostCommitInsertEventListeners = new IPostInsertEventListener[] { new AuditEventPostInsert() }) .ExposeConfiguration(c => c.EventListeners.PostCommitUpdateEventListeners = new IPostUpdateEventListener[] { new AuditEventPostUpdate() }); 
0
source

All Articles