CHAPTER 8
In this chapter, we will look at what NHibernate has to offer when it comes to changing some of its default behavior and getting notified when some events occur.
NHibernate offers a mechanism by which we can intercept, among others:
Interceptors are a complex mechanism. Let’s look at two simple examples—one for changing the SQL and the other for injecting behavior dynamically into entity classes loaded from records.
An interceptor is injected on the Configuration instance; only one can be applied at a time and that must be before building the session factory.
A typical implementation of a custom interceptor might inherit from the NHibernate.EmptyInterceptor class which is a do-nothing implementation of the NHibernate.IInterceptor interface:
Tip: You need to reference the NHibernate.SqlCommand namespace for the SqlString class.
This simple example allows you to send SQL commands before and, optionally, after any other:
For more complex scenarios, you would have to parse the SqlString parameter and either insert, remove or replace any contents on your own.
A more interesting example is making use of NHibernate’s built-in proxy generator—the one it uses to build lazy loading proxies. This is a way to automatically add a proper implementation of INotifyPropertyChanged. You might be familiar with this interface, which is used, for example, in WPF data binding where a control needs to be notified of any changes that occur to its data source’s properties so that it can redraw itself. Implementing INotifyPropertyChanged has absolutely no complexity but it is a lot of work if we have many properties. Besides that, it forces us to use backing fields for properties. Here is the code for an interceptor that makes all loaded entities implement INotifyPropertyChanged:
Tip: You need to reference the NHibernate, NHibernate.Type, System.ComponentModel, and System.Reflection namespaces.
Its registration is as simple as:
cfg.SetInterceptor(new NotifyPropertyChangedInterceptor()); |
And a sample usage:
Granted, this code is a bit complex. Nevertheless, it isn’t hard to understand:
Listeners are NHibernate’s events; they allow us to be notified when something occurs. It turns out that NHibernate offers a very rich set of events that cover just about anything you might expect—from entity loading, deletion and saving, to session flushing and more.
Multiple listeners can be registered for the same event; they will be called synchronously at specific moments which are described in the following table. The table lists both the code name as well as the XML name of each event. I have also included the name of the property in the Configuration.EventListeners property where the listeners can be added by code.
The full list of events is:
Event | Description and Registration Property |
Autoflush/auto-flush | Called when the session is flushed automatically (AutoFlushEventListeners property) |
Create/create | Called when an instance is saved (PersistEventListeners) |
CreateOnFlush/ create-onflush | Called when an instance is saved automatically by a Flush operation (PersistOnFlushEventListeners) |
Delete/delete | Called when an entity is deleted by a call to Delete (DeleteEventListeners) |
DirtyCheck/dirty-check | Called when a session is being checked for dirty entities (DirtyCheckEventListeners) |
Evict/evict | Called when an entity is being evicted from a session (EvictEventListeners) |
Flush/flush | Called when a Flush call occurs or a transaction commits, after FlushEntity is called for each entity in the session (FlushEventListeners) |
FlushEntity/flush-entity | Called for each entity present in a session when it is flushed (FlushEntityEventListeners) |
Load/load | Called when a session is loaded either by the Get/Load method or by a query, after events PreLoad and PostLoad (LoadEventListeners) |
LoadCollection/ load-collection | Called when an entity’s collection is being populated (InitializeCollectionEventListeners) |
Lock/lock | Called when an entity and its associated record are being locked explicitly, either by an explicit call to the Lock method or by passing a LockMode in a query (LockEventListeners) |
Merge/merge | Called when an existing entity is being merged with a disconnected one, usually by a call to Merge (MergeEventListeners) |
{Pre/Post}CollectionRecreate/{pre/post}-collection-recreate | Called before/after a bag is being repopulated, after its elements have changed ({Pre/Post}CollectionRecreateEventListeners) |
{Pre/Post}CollectionRemove/{pre/post}-collection-remove | Called before/after an entity is removed from a collection ({Pre/Post}CollectionRemoveEventListeners) |
{Pre/Post}CollectionUpdate/{pre/post}-collection-update | Called before/after a collection was changed ({Pre/Post}CollectionUpdateEventListeners) |
PostCommitDelete/ post-commit-delete | Called after a delete operation was committed (PostCommitDeleteEventListeners) |
PostCommitInsert/ post-commit-insert | Called after an insert operation was committed (PostCommitInsertEventListeners) |
PostCommitUpdate/ post-commit-update | Called after an update operation was committed (PostCommitUpdateEventListeners) |
{Pre/Post}Delete/ {pre/post}-delete | Called before/after a delete operation ({Pre/Post}DeleteEventListeners) |
{Pre/Post}Insert/{pre/post}-insert | Called before/after an insert operation ({Pre/Post}InsertEventListeners) |
{Pre/Post}Load/{pre/post}-load | Called before/after a record is loaded and an entity instance is created ({Pre/Post}LoadEventListeners) |
{Pre/Post}Update/{pre/post}-update | Called before/after an instance is updated ({Pre/Post}UpdateEventListeners) |
Refresh/refresh | Called when an instance is refreshed (RefreshEventListeners) |
Replicate/replicate | Called when an instance is being replicated (ReplicateEventListeners) |
Save/save | Called when an instance is being saved, normally by a call to Save or SaveOrUpdate but after PostInsert/PostUpdate (SaveEventListeners) |
SaveUpdate/save-update | Called when an instance is being saved, normally by a call to SaveOrUpdate but after PostUpdate (SaveOrUpdateEventListeners) |
Update/update | Called when an instance is being updated explicitly, by a call to Update (UpdateEventListeners) |
An event listener needs to be registered in the Configuration instance prior to creating a session factory from it:
//register a listener for the FlushEntity event cfg.AppendListeners(ListenerType.FlushEntity, new IFlushEntityEventListener[]{ new ProductCreatedListener() }); |
It is also possible to register event handlers by XML configuration; make sure you add an assembly qualified type name:
<session-factory> <!-- … --> <listener type="flush-entity" class="Succinctly.Console.ProductCreatedListener, Succinctly.Console"/> </session-factory> |
Let’s look at two examples, one for firing a domain event whenever a new product is added and the other for adding auditing information to an entity.
Here’s the first listener:
Tip: Add a using declaration for namespace NHibernate.Event.
An an example usage:
As for the auditing, let’s start by defining a common interface:
|
{ String CreatedBy { get; set; } DateTime CreatedAt { get; set; } String UpdatedBy { get; set; } DateTime UpdatedAt { get; set; } } |
The IAuditable interface defines properties for storing the name of the user who created and last updated a record, as well as the date and time of its creation and last modification. The concept should be familiar to you. Feel free to add this interface to any of your entity classes.
Next, the listener that will handle NHibernate events and fill in the auditing information:
As for the registration code, it is a little more complex than the previous example:
In XML:
<listener type="save" class="Succinctly.Common.AuditableListener, Succinctly.Common"/> <listener type="save-update" class="Succinctly.Common.AuditableListener, Succinctly.Common"/> <listener type="update" class="Succinctly.Common.AuditableListener, Succinctly.Common"/> <listener type="flush-entity" class="Succinctly.Common.AuditableListener, Succinctly.Common"/> <listener type="merge" class="Succinctly.Common.AuditableListener, Succinctly.Common"/> |
The AuditableListener class allows you to specify a delegate property for obtaining the current date and time (CurrentDateTimeProvider) and the name of the current user (CurrentIdentityProvider). It must be registered as a listener for several events (Save, SaveOrUpdate, Update, FlushEntity, and Merge) because several things can happen: