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
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.
"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.
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?
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?
Also, using the ASP.NET session doesn't scale.
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.
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...
Simple long-conversation management
code.google.com/.../NHExtendedSessionWebModule.cs
Restriction: only one long conv. allowed
This is the other pattern
fabiomaulo.blogspot.com/.../...nversation-per.html
Note: the usage of long conversation are not recommended in applications where the hardware scalability is mandatory. session-per-request is the better pattern in WEB and and the design of the WEB app should have it in mind.
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.
@Tyler
Exactly what I mean with "WEB app should have it in mind".
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.
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...
Rik,
Caching?
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.
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.
I'm with you Tyler. Keep it simple. My head hurts from all the mental gymnastics being discussed.
@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.
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
Ayende,
What if the user didn't change anything will the row still be updated?
DaRage,
If so, to its original values, with is a no op anyway, so you wouldn't care.
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.
DaRage,
Yes, it probably will.
If you care about that, take a look at Merge
Set,
You mentioned avoiding session state. Just wondering how you usually implement long conversations without using session state?
Comment preview