Enable multiple child sessions per root session

Description

During composite operations, multiple independent business-layer components may need to share DB connection and transaction while using completely separate session instances in order to remain context-agnostic and avoid any adverse effects and cross-interference, both among themselves and with/from the root scope of the operation.
In practice, a business component must be able to act on the presumption that any session it obtains could and should be used as if it were standalone and not part of any ongoing operation of a greater scope.
Contrary to this desired pattern, the existing SessionImpl.GetSession() method returns a singleton child session, which might then be shared by unsuspecting and otherwise decoupled business components, potentially causing random unpredictable behaviors due to the shared first-level entity cache.

Environment

None

Activity

Show:

Alex Zaytsev 
June 20, 2017 at 10:41 AM

Ok. Done Thanks

Frédéric Delaporte 
June 20, 2017 at 10:40 AM

In fact I wanted to mark it as obsolete, but not done the right thing.

Alex Zaytsev 
June 20, 2017 at 10:27 AM

what did you mean by closing the issue?

Frédéric Delaporte 
May 8, 2017 at 9:34 AM

The refactoring of session constructor () has introduced a new method on ISession: WithSessionOptions(), which yield a session builder. This session builder can optionally take part of the session from it was created to build as many new sessions as required.

Semantic change: these sessions are not flushed or closed from the originating session.

Alex Lobakov 
April 21, 2017 at 6:48 PM

I think these are a little different. When we talk about the classical TransactionScope, it's typical use case is in the context of a model where one session is shared by multiple business components, each of which needs to have its data manipulations wrapped in a robust unit of work (all or nothing).
And then there's typically a top-level transaction scope that must eventually commit the changes.
Now, the very fact that a session is being shared may be both good and evil, but what's definitive about it is that it makes things very circumstantial. E.g. there's a number of scenarios I've come across where one absolutely needed to perform ISession.Flush(), and here's the caveat: if you're using a shared session, you just don't know what exactly you're flushing now that some other components may have made their modifications to the same entity cache without flushing.
Moreover, when a business component gets called and uses a shared session, it doesn't have any guarantees as to the current state of the session, and may find certain data in a condition quite different than what it would be, had a flush been performed prior to to the call. All this undermines the principle of making components context-agnostic. The outcome of any particular method must not depend on circumstances that are external and unrelated to the business logic of the method. Which effectively brought me to the realization that sharing a session in even a moderately complex application may not be such a good idea after all. As I see it now after many years and projects with NH, I'd rather obtain a 100% clean session in a business component's public/interface method, do whatever needs to be done and flush that work right there to persist changes to the DB (even if not committing them just yet), so that any other business components executing within the same call context can reliably expect that:
a) everything the method has done is now available/visible in the DB, as these sessions are part of the same connection/transaction context;
b) everything the method has done was based on its view of persisted data, as opposed to the unpredictable view of an unflushed ambient session cache;
c) the method has made zero modifications to any other sessions.
And that's our ultimate black-boxing! The only things that are now shared by those independent sessions are the physical connection and transaction.
To me, this clarity and predictability easily surpasses any benefits of last-minute flushing in a shared session. And as an extra perk, I get the ability to use a per-session TransactionScope, which I can now safely Complete() before disposing the session, as everything that ever needed to go into the database from the session will have already been explicitly flushed by then.
Obviously, I'm not doing all this manually, but rather use my own wrapper UnitOfWork class which handles both session acquisition/disposal and the transaction scope pattern. - Hence, to support multiple child sessions.

Obsolete

Details

Assignee

Reporter

Labels

Components

Affects versions

Priority

Who's Looking?

Open Who's Looking?
Created April 15, 2017 at 7:43 PM
Updated June 20, 2017 at 10:41 AM
Resolved June 20, 2017 at 10:40 AM
Who's Looking?