Ayende @ Rahien

It's a girl

Nhibernate Unit of Work & multiple reuqests

Another post request from the forum:

Oren,

I'd love to get some detail on how to make the most of NHibernate's unit of work capability in a web app scenario.

It seems like in a web app, because your unit of work may be split across multiple web requests (and therefore sessions), it's hard to use NHibernate's change tracking etc effectively and I find myself doing unit of work tracking myself.

This seems to lead inevitably to an anaemic domain model and feels like fighting the tool.

Well, I am going to take that in two parts. First, having a unit of work that span requests is something that is possible (and I’ll discuss how to implement shortly), but I don’t recommend it. It is much easier to build your application so each request is a single unit of work.

Now, about actually implement a request that spans multiple requests, it is not really hard. It is called long conversation, or session per business transaction. Here is a detailed explanation on how NHibernate Burrow is achieving this.

But basically, it is quite simple. You set the session’s flush mode to Never, and you store NHibernate’s session in the ASP.Net session. Now, for each request, you can get the NHibernate session for the ASP.Net session and continue working with the same session and entities, taking advantage of change tracking, transparent persistence and all the other features that make NHibernate easy.

There are several frameworks out there that would handle this for you, Burrow, as I mentioned and Rhino Commons as well.

Comments

Rafal
08/13/2009 10:56 AM by
Rafal

Hi, having such feature is very convenient. In the company I'm working for we are using 'Sooda' ORM and it's capable of serializing and deserializing transaction state. Using this we can implement long living 'transactions' that span multiple http requests. Transaction state is serialized to xml files, so even in case of application restart users will not lose their uncommited data and can continue working as if nothing happened. BTW by transaction state I mean the state of all objects inserted or modified in single ORM session. I havent yet tried to do the same with NHibernate, but hope it's possible.

Rik Hemsley
08/13/2009 12:01 PM by
Rik Hemsley

"It is much easier to build your application so each request is a single unit of work."

When a user is 'building' a possibly-temporary entity from the (web) UI, they may cause dozens of requests when moving between pages and with AJAX.

I'm interested to know how you (Oren) handle this common situation without a unit of work which spans HTTP requests.

Kelly Stuard
08/13/2009 01:48 PM by
Kelly Stuard

I'm curious how you have the UOW span multiple requests when you are in a multi-server webfarm. Does nHib session work ok when de-/re-serialized through the out-of-proc session state providers?

Neil Barnwell
08/13/2009 02:11 PM by
Neil Barnwell

You suggest putting the NHibernate session in the ASP.NET session, but is that really a good idea? What if you are load balancing the web servers? What if you are using SQL Session Persistence?

DaRage
08/13/2009 02:19 PM by
DaRage

Also, using the ASP.NET session doesn't scale.

Kelly Stuard
08/13/2009 02:20 PM by
Kelly Stuard

On a side note: the "future posts" section gives me something to look forward to. For instance, the "8 days: Help requests that make me want to cry" sounds like a good one.

Set
08/13/2009 02:38 PM by
Set

but is that really a good idea?

From my point of view, I hate aspnet session, now I avoid these like plague, it's not yet to the point of viewstate and dataset but I feel it'll be soon reaching that point...

Unfortunnatly, at work I've seen SqlTransaction objects being serialized inside asp.net session inside web services... :(

Happily that I don't have to look much into the code of these WS...

Tyler Burd
08/13/2009 04:28 PM by
Tyler Burd

For requests that span multiple pages I generally don't write the data to the actual entity until the very last screen is submitted. I keep track of the previous pages' data via hidden form fields. OR, I create a separate persistent model solely to save the user's progress. When the last screen is complete I then copy that "form" entity's data into the "business" entity.

This does seem like more work at the outset, but it's a hell of a lot easier than managing a long conversation by dropping the NH session into the ASP session and all of the problems with concurrency that creates.

Fabio Maulo
08/13/2009 04:34 PM by
Fabio Maulo

@Tyler

Exactly what I mean with "WEB app should have it in mind".

DaRage
08/13/2009 05:09 PM by
DaRage

Question: how does the session per request deal with this very common scenario: a user retrieve the data for viewing in the first request and edits and submit his changes in a second request.

You can argue to re-attach the entity on the second request but you will lose all the tracking information.

vbedegi
08/13/2009 05:16 PM by
vbedegi

Right now, I am searching for the holy pattern to span a session through multiple requests. One approach could be to record every request, that modifies the entity, and before each request, just replay (re-apply) them on the entity. This would work for very simple scenarios, but often, the commands take effect on a whole aggregate, not just a standalone entity.

So now, the session-per-conversation pattern seems to be the most viable solution for me, with all of it's limitations. Until you suggest me a better one...

Ayende Rahien
08/13/2009 06:40 PM by
Ayende Rahien

Neil,

If that is the case, then you need to make sure that your entities are serializable. The session itself is.

Theoretically, you can serialize it back & forth between servers.

In practice, I would feel much better if you used sticky sessions, though.

Ayende Rahien
08/13/2009 06:48 PM by
Ayende Rahien

Rafal & Kelly,

Yes, NH support serialization of the session.

Kelly,

Wait for it, really wait for it.

Set,

SqlTransaction inside ASP.Net Sessions.

You make me want to cry again.

DaRage,

You use versioning, and just update the instance. NH takes care of everything else.

vbedegi,

It seems like if you can't use session per conversation, you can just keep the state on the client, no need to do applications of diffs.

Mr_Simple
08/13/2009 07:07 PM by
Mr_Simple

I'm with you Tyler. Keep it simple. My head hurts from all the mental gymnastics being discussed.

DaRage
08/13/2009 07:23 PM by
DaRage

@Ayende

can you explain more? how does NH know if the entity has changed? does it fetch from the db and compare record by record and update if something has changed? are you suggesting to increment the version number manually? isn't the version number number manipulated internally by NH?

please explain more. Thanks.

Ayende Rahien
08/13/2009 07:31 PM by
Ayende Rahien

DaRage,

No need to do anything else, just materialize the values from the client (including the version number that the user editted) and call Update on it.

That would cause NH to update the row, and we use the version number to ensure concurrency control

DaRage
08/13/2009 07:48 PM by
DaRage

Ayende,

What if the user didn't change anything will the row still be updated?

Ayende Rahien
08/14/2009 12:10 AM by
Ayende Rahien

DaRage,

If so, to its original values, with is a no op anyway, so you wouldn't care.

DaRage
08/14/2009 01:27 PM by
DaRage

Ayende,

But it will still increment the version number, right?

The reason I asked this is that if I wrap the 2 requests in a conversation and keep the original NH session in ASP.NET session, then if the use makes no changes to the entity NH will not update it.

so it's kind of we have to use conversations anyway because they're very common just like in this scenario and the session-per-request pattern is not sufficient in most cases.

Please correct me if I'm wrong.

Ayende Rahien
08/14/2009 09:27 PM by
Ayende Rahien

DaRage,

Yes, it probably will.

If you care about that, take a look at Merge

Imran
09/01/2009 12:04 PM by
Imran

Set,

You mentioned avoiding session state. Just wondering how you usually implement long conversations without using session state?

Comments have been closed on this topic.