As I was playing around with NHibernate today, I came accross a rather inconvenient problem. :).
Let me first explain what I wanted to achieve:
For every domain object that I save, I want to persist in the database when the entity has been created, when it has been last updated and by whom. Nothing special, just regular audit-information.
To make this all possible, I've created the following classes / interfaces:
IAuditableinterfaceAuditableEntityinterface
I think this is pretty straightforward and doesn't require any further explanation.
Then, I continued with creating an NHibernate interceptor which would set the Created and Updated dates. (I could also used the ILifecycle interface instead, but this meant that I would have a dependency to the NHibernate assembly in my 'domain classes assembly', and I don't like that. In fact, the ILifecycle interface has been deprecated for exactly that reason).
This is an extract from my AuditInterceptor which would perform the task I wanted (at least, I thought so ... ).
(Note that my AuditInterceptor is NOT in the same assembly where the IAuditable, AuditableEntity and other domain base class reside in. This would create a dependency from my base classes to NHibernate and again, I hate this :) ).
The AuditInterceptor (snippet):
As you can see, it is very simple: I only had to implement 2 methods of the IInterceptor interface:
OnSave, which is called when an entity is saved for the first time in the database (INSERT)OnFlushDirty, which is called when an existing entity is dirty and has to be updated
IAuditable interface, and if so, I just set the necessary properties (Created and Updated) to the appropriate values (the current DateTime).Easy enough, simple, understandable and clean... If only this would work...
During testing, I got the following exception:
----> System.Data.SqlTypes.SqlTypeException : SqlDateTime overflow.
Must be between 1/1/1753 12:00:00 AM and 12/31/9999 11:59:59 PM.
at NHibernate.Persister.Entity.AbstractEntityPersister.Insert(Object[] fields,
Boolean[] notNull, SqlCommandInfo sql, Object obj, ISessionImplementor session)
As it turns out, NHibernate doesn't 'see' the changes you make to the entity parameter that is passed to the Interceptor methods:
You can however, change the values that are in the state array parameter. Then NHibernate will correctly persist the changes.
But, I do not like to 'hard-code' property names as strings for obvious reasons (if you change a property, the compiler will not detect that you should change your 'hardcoded property name string', etc...).
Anyway, in order to get my interceptor to work, I have no other choice then messing around with the propertyNames[] and state[] parameters.
In order to get rid of the 'weak-typing', I added a little bit more code.
So, now my classes look like this:
IAuditableinterfaceAuditableEntityclassAuditInterceptor
This solution is, IMHO, elegant enough to live with, and it works.
However, maybe someone else has a better, more elegant solution for this ? If so, I'd like to hear from you ...
6 comments:
We hav been strugling with the same issue (http://elegantcode.com/2008/05/15/implementing-nhibernate-interceptors/). The solution we came up with is almost the same as the one you are proposing. Wouldn't know for a better solution. Maybe the new event listeners in NH 2.0 can bring more to the table.
Hi.
I also don't think that there's another solution for the problem.
Interesting blog you have there, btw.
You can use the timestamp or version
http://www.hibernate.org/hib_docs/nhibernate/html_single/#mapping-declaration-timestamp
From nhibernate.
Otherwise, why have a version and timestamp, both at the same time and not just one of them? Timestamp = versioning, right? Unless it's a business concept; the version, but then it seems misplaced in IAuditable.
@henrik
Hmm, in a way, you're right.
The Updated property is just a regular DateTime which indicates when an entity has last changed.
The Version property is there for versioning / optimistic concurrency. I indeed use the version element in my NHibernate mapping file to implement this versioning.
I choose to not use the Updated property for the versioning-mechanism, since I think that using a datetime for this purpose could be a little bit unsafe in some situations. (2 updates occurring at exactly the same time).
Also, the supported types for the Version element are Int16, Int32, Int64, Ticks and Timespan.
The Timespan element in NHibernate could be interesting, especcially since you have the option to specify that it should be generated by the DB. However, I think that this timestamp is not implemented as a regular DateTime, but as a Timestamp ?
Actually henrik, your post made me think ...
Actually, the AuditInterceptor should only be responsible for saving who has created the entity, and who has last updated it. (So, only the LastUpdatedBy and CreatedBy properties should be set via the interceptor).
The reason why I think that, is that the clock of the (database) server should be used to determine the date and time when the entity has been created and updated.
A way to implement this, would be to use the Timestamp element in your NHibernate mapping file to get the LastUpdatedDateTime of the entity (but, this means you cannot use the Version element anymore).
For the Created property, we could use a datetime-column in the database which has a DEFAULT constraint on it.
But, maybe there are people out there with a different opinion that they'd like to share. :)
Een reactie plaatsen