An nHibernate session maintains all changes to the object model. At some point, it needs to synchronize these changes with the database. It can either do these constantly (as and when it encounters a new change in the object model) – or it can wait till it has a whole batch of changes to commit. The latter is an obvious choice – since it avoids multiple database roundtrips. This ‘synchronization’ is called flushing and it happens at various points in a session’s lifecycle. While it is a good thing, if you have TOO MUCH flushing, you may end up with a performance degradation.
For example, if you have a complex query (which gets broken into many smaller queries by nHibernate), you drastically increase the number of times that session.flush() is called by nHibernate. The problem is :
Each session.flush() call is very expensive.
For your complex queries, you may be looking at a few dozen times that this session.flush() call takes place. This really slows down the round trip query execution time (although in reality, the actual SQL execution has nothing to do with it – it is simply nHibernate’s bookkeeping).
Turn off automatic flushing
This is the single , most important performance improvement you can make in nHibernate. Turning automatic flushing off means you will manually have to decide when and where to flush(). This is simple – just before you start your transaction make sure everything is in sync by flushing once. Now turn off automatic flushing. And right before you are ready to commit your transaction (after you have saved your session), go ahead and turn flushing back on. Here is a sample :
Just before you start your transaction
- Manually flush and
- Turn off auto flushing
Just before you commit,
- Turn automatic flushing back on
What can go wrong?
Remember, you are temporarily suspending synchronizing the database with your object changes. This is not an issue for your retrieval (get) queries, but for saves, updates, deletes – you need to watch out for specific scenarios where you may have transient data (data that is not yet committed – but you try to access anyway). An example of such a transient piece of data is a private property ID (private so that the database autogenerates the ID). If you have an ID that is to be autogenerated – and you try to access that ID before the commit takes place, you will end up in this ‘exception’ scenario. Suppose your logic entails creating a new Product (say a new Financial Product Offering) – and then associating existing customers with it (perhaps as PROSPECTS for this product). Since your productID is not yet committed, associating the customers will not work – and will throw an exception of the following type:
NHibernate.TransientObjectException: object references an unsaved transient instance – save the transient instance before flushing or set cascade action for the property to something that would make it autosave. Type:
All this means is that nHibernate does not know what to do with the object changes (since flushing has been turned off). To counter this, you must revisit your business logic and try and figure out why you needed to access something that hadn’t been committed yet. If possible, change the business logic. If not possible, then simply remove the manual flushing code above and revert to automatic flushing. This scenario, though possible, is more of an exception than the rule – and generally speaking, the manual flushing is a safe change to make.
In addition to revisiting your business logic, one recommended change (for your transaction.commit()) is to surround it with a try-catch. The try catch will allow a rollback in case you end up with any failed parts within your transaction. All you need is :
After all, rollbacks are the reason you are using transactions in the first place. If something goes wrong, you have the option of rolling back the transaction to avoid inconsistent states.
There are a few areas where nHibernate settings can affect your data tier performance. The most significant one that I discovered (during a production troubleshooting assignment of a web app) was this automatic and frequent session flushing. This can be easily circumvented by a few lines of code change. It does speed things up – but comes with a potential drawback – which affects saves and updates (not the retrievals). The saves and updates can possibly run into a situation where nHibernate does not know what to do with a transient object. It throws a meaningful exception – which can be addressed using the suggestions above.
In spite of this potential drawback, manual session flushing is something I would still recommend – due to the significant performance gain. The files affected by the potential drawback can be easily addressed by either disabling manual flushing – or reworking the business logic that causes the exception to occur.