Nested TransactionScope with RequiresNew: unexpected behavior

332 views
Skip to first unread message

HH

unread,
Jan 14, 2010, 3:25:43 PM1/14/10
to nhusers
Hi All,

I'am trying to find out the best practices for using
System.Transactions.TransactionScope with NHibernate.
I have created some unit tests to find out what works and what doesn't
work. I am stuck with a couple of questions:

First, when I execute the following code (sorry for dutch naming):

[TestMethod]
public void CanCommitNestedTransactionWhenOuterTransactionIsAborted()
{
long medewerkerId1 = 2;
long medewerkerId2 = 3;
string voornaam1;
string voornaam2;

// NHibernate Session will be openend on first call to repository

IMedewerkerRepository repository = this.CreateRepository();

using (TS.TransactionScope scope1 = new TS.TransactionScope()) {
// Update m1 in scope1.
Medewerker m1 = repository.Get(medewerkerId1);
voornaam1 = m1.Voornaam;
m1.Voornaam = DateTime.Now.ToString();
repository.Update(m1);

// Update m2 in nested scope2 -> RequiresNew
using (TS.TransactionScope scope2 = new TS.TransactionScope
(TransactionScopeOption.RequiresNew)) {
Medewerker m2 = repository.Get(medewerkerId2);
voornaam2 = m2.Voornaam;
m2.Voornaam = DateTime.Now.ToString();
repository.Update(m2);

scope2.Complete(); // scope 2 is completed
}

//NB: scope1 is not completed
}

Medewerker mCheck1 = repository.Get(medewerkerId1);
Medewerker mCheck2 = repository.Get(medewerkerId2);

Assert.AreEqual(voornaam1, mCheck1.Voornaam);
Assert.AreNotEqual(voornaam2, mCheck2.Voornaam);

// TestCleanup will dispose session
}

So scope1 should not be commited and scope2 should (in my opinion) be
committed because it is a seperate transaction (RequiresNew). To my
supprise both scopes are not committed, why, isn't RequiresNew
supported by NHibernate?

Second question:

Consider the following test:

[TestMethod]
public void CanRollbackTransaction() {
long medewerkerId = 2;
string voornaam;

// NHibernate Session will be openend on first call to repository

IMedewerkerRepository repository = this.CreateRepository();

using (TS.TransactionScope scope = new TS.TransactionScope()) {
Medewerker m = repository.Get(medewerkerId);
voornaam = m.Voornaam;

m.Voornaam = DateTime.Now.ToString();

repository.Update(m);

//NB: scope is not completed
}

Medewerker mCheck = repository.Get(medewerkerId);

Assert.AreEqual(voornaam, mCheck.Voornaam);

// TestCleanup will dispose session
}

This test does not pass although scope is rollbacked in the database.
It looks like the session cache does not get updated (no call to db in
NHProf). The second "Get" gets it's value from the cache and that
still is the updated (but rollbacked) data. When I create my own
wrapper arround TransactionScope and call ISession.Clear() on the
current NHibernate session the test passes. But is this the way to go?

I am a bit stuck on how to use System.Transactions.TransactionScope
with NHibernate. I need to use the TransactionScope class because of
other db activity outside NHibernate in the same transaction.

Kind regards,
Henk Huisman

Fabio Maulo

unread,
Jan 14, 2010, 4:29:38 PM1/14/10
to nhu...@googlegroups.com
mmm.... where is the Begin and Commit of NH's transaction ?

2010/1/14 HH <hwhu...@gmail.com>
--
You received this message because you are subscribed to the Google Groups "nhusers" group.
To post to this group, send email to nhu...@googlegroups.com.
To unsubscribe from this group, send email to nhusers+u...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/nhusers?hl=en.






--
Fabio Maulo

Fabio Maulo

unread,
Jan 14, 2010, 4:31:08 PM1/14/10
to nhu...@googlegroups.com
I mean... you can open the session outside the TransactionScope but, inside it, you should Begin a NH's transaction.

2010/1/14 Fabio Maulo <fabio...@gmail.com>



--
Fabio Maulo

Jason Meckley

unread,
Jan 14, 2010, 4:32:04 PM1/14/10
to nhusers
Since you are spiking NH and TS behavior the first thing I would do is
remove the repository abstraction and work with NH directly.
IIRC The TS needs to be defined before opening the session.
using(var ts = new TransactionScope())
using(var session = factory.OpenSession())
{
session.Save(new Entity());
ts.Complete();
}
start from this point and add tests to see what the results are.

HH

unread,
Jan 14, 2010, 4:41:08 PM1/14/10
to nhusers
Hi Fabio,
I am not using the NHibernate transactions. I thought I should not
have to because NHibernate will enlist in the TransactionScope
transaction:
http://ayende.com/Blog/archive/2006/06/04/NHibernateAndSystemTransactionsASuccess.aspx

A little more background:
I am using the "OpenSessionInView" method for my session handling in
my asp.net MVC application.
However, the session is opened when it is first needed and is disposed
in the Application_EndRequest method.
I am not opening any NHibernate transactions though.

Do you have any ideas on how to make this work?

Henk


On 14 jan, 22:29, Fabio Maulo <fabioma...@gmail.com> wrote:
> mmm.... where is the Begin and Commit of NH's transaction ?
>

> 2010/1/14 HH <hwhuis...@gmail.com>

> > nhusers+u...@googlegroups.com<nhusers%2Bunsu...@googlegroups.com­>


> > .
> > For more options, visit this group at
> >http://groups.google.com/group/nhusers?hl=en.
>
> --

> Fabio Maulo- Tekst uit oorspronkelijk bericht niet weergeven -
>
> - Tekst uit oorspronkelijk bericht weergeven -

Fabio Maulo

unread,
Jan 14, 2010, 4:47:12 PM1/14/10
to nhu...@googlegroups.com
use it and let me know the difference.
NOTE to everybody : session.BeginTransaction mean begin a NH's transaction.
NH has two transactionFactory and the default will take care about TS enlisting itself in the TS and will take care about session flush/closeand the others stuff.

2010/1/14 HH <hwhu...@gmail.com>
To unsubscribe from this group, send email to nhusers+u...@googlegroups.com.

For more options, visit this group at http://groups.google.com/group/nhusers?hl=en.






--
Fabio Maulo

Fabio Maulo

unread,
Jan 14, 2010, 4:49:18 PM1/14/10
to nhu...@googlegroups.com
2010/1/14 HH <hwhu...@gmail.com>


Do you have any ideas on how to make this work?

with an appropriate ActionFilter to manage NH's session, TS and NH's transaction 

Fabio Maulo

HH

unread,
Jan 14, 2010, 5:57:23 PM1/14/10
to nhusers
So you are suggesting that I should use the following:

OnActionExecuting:
1) Open Session (if not open)
2) CreateTransactionScope
3) Start NHibernate Transaction

OnActionExecuted:
if ([errorsOccured])
Rollback NHibernate transaction
else
Commit NHiberate transaction
TransactionScope.Complete()

Session will be closed on end request.

This makes me have to manage both the TransactionScope and NHibernate
transaction.

Why?

Henk

On 14 jan, 22:49, Fabio Maulo <fabioma...@gmail.com> wrote:
> 2010/1/14 HH <hwhuis...@gmail.com>

Fabio Maulo

unread,
Jan 14, 2010, 6:35:56 PM1/14/10
to nhu...@googlegroups.com
you can commit the NH's transaction and leave the real action to TS (Abort/Complete).
Why ?...
Well... you should manage the NH's always and occasionally the NH's will work in an Ambient-Transaction.
The code where the NH's transaction is managed shouldn't not be aware about if it is working in Ambient-Transaction or not.
For those using AOP this is pretty normal.

btw, have you tried using the NH's transaction in your tests ?

2010/1/14 HH <hwhu...@gmail.com>
--
You received this message because you are subscribed to the Google Groups "nhusers" group.
To post to this group, send email to nhu...@googlegroups.com.
To unsubscribe from this group, send email to nhusers+u...@googlegroups.com.

For more options, visit this group at http://groups.google.com/group/nhusers?hl=en.






--
Fabio Maulo

Jason Meckley

unread,
Jan 14, 2010, 9:48:15 PM1/14/10
to nhusers
I have run into errors with TransactionScope and Session/Transaction
handling. what I have seen occur is the transaction is started on one
thread, but attempts to close on another and NH didn't like this.
Instead of TS I manage both NH factories/sessions/transactions either
both transactions commit or rollback.


On Jan 14, 6:35 pm, Fabio Maulo <fabioma...@gmail.com> wrote:
> you can commit the NH's transaction and leave the real action to TS
> (Abort/Complete).
> Why ?...
> Well... you should manage the NH's always and occasionally the NH's will
> work in an Ambient-Transaction.
> The code where the NH's transaction is managed shouldn't not be aware about
> if it is working in Ambient-Transaction or not.
> For those using AOP this is pretty normal.
>
> btw, have you tried using the NH's transaction in your tests ?
>

> 2010/1/14 HH <hwhuis...@gmail.com>

> > nhusers+u...@googlegroups.com<nhusers%2Bunsu...@googlegroups.com>

Jason Meckley

unread,
Jan 14, 2010, 11:38:10 PM1/14/10
to nhusers
reading my previous post I realized I left out the context of when
this happened. It was with web (monorail) and a IHttpModule.

HH

unread,
Jan 15, 2010, 2:35:33 AM1/15/10
to nhusers
Hi Fabio,

I am still a bit confused. Now I am using the following test code:

[TestMethod]
public void CanRollbackTransaction() {
long medewerkerId = 2;
string voornaam;

using (var session = NHibernateSession.Current) // session will be
opened
using (NHibernate.ITransaction trans = session.BeginTransaction()) {

using (var ts = new TransactionScope()) {

Medewerker m = session.Get<Medewerker>(medewerkerId);


voornaam = m.Voornaam;
m.Voornaam = DateTime.Now.ToString();

session.Update(m);

//NB: scope is not completed
}

trans.Commit(); // This throws NHibernate.TransactionException:
Transaction not connected, or was disconnected.
}

using (var session = NHibernateSession.Current) {
Medewerker mCheck = session.Get<Medewerker>(medewerkerId);
Assert.AreEqual(voornaam, mCheck.Voornaam);
}
}

The above code does not succeed when commiting the NHibernate
Transaction I receive the following (obvious) error:
NHibernate.TransactionException: Transaction not connected, or was
disconnected.

Next test looked like this:

[TestMethod]
public void CanRollbackTransaction() {
long medewerkerId = 2;
string voornaam;

string voornaamCheck;

using (var session = NHibernateSession.Current) {
using (var ts = new TransactionScope()) {
using (NHibernate.ITransaction trans = session.BeginTransaction())
{

Medewerker m = session.Get<Medewerker>(medewerkerId);


voornaam = m.Voornaam;
m.Voornaam = DateTime.Now.ToString();

session.Update(m);

trans.Commit();
}

//NB: scope is not completed
}

using (NHibernate.ITransaction trans = session.BeginTransaction()) {
Medewerker mCheck = session.Get<Medewerker>(medewerkerId);
voornaamCheck = mCheck.Voornaam;

trans.Commit();
}
Assert.AreEqual(voornaam, voornaamCheck);
}
}

So NHTransaction nested in TS and check on equalness in same session
(not same transaction). Again no succes. The Session.Get gives me the
"wrong" value.

My main issue is that the NHibernate action are only part of the
solution, so when writing a "Transaction" ActionFilterAttribute for
the controllers of my MVC classes I want to make sure that all actions
in the controller succeed or do not succeed. So I must use TS as the
main transaction handler and I want NHibernate transactions to be part
of the "main transaction" that is the TS transaction.

Any suggestions?

Henk

On 15 jan, 00:35, Fabio Maulo <fabioma...@gmail.com> wrote:
> you can commit the NH's transaction and leave the real action to TS
> (Abort/Complete).
> Why ?...
> Well... you should manage the NH's always and occasionally the NH's will
> work in an Ambient-Transaction.
> The code where the NH's transaction is managed shouldn't not be aware about
> if it is working in Ambient-Transaction or not.
> For those using AOP this is pretty normal.
>
> btw, have you tried using the NH's transaction in your tests ?
>

> 2010/1/14 HH <hwhuis...@gmail.com>

> > nhusers+u...@googlegroups.com<nhusers%2Bunsu...@googlegroups.com­>


> > .
> > For more options, visit this group at
> >http://groups.google.com/group/nhusers?hl=en.
>
> --

Jason Meckley

unread,
Jan 15, 2010, 8:42:43 AM1/15/10
to nhusers
if you use TS and open the session after the TS is defined. you should
not need to explicitly manage transactions.

using(var ts = new TransactionScope())
using(var session = factory.OpenSession())
{
session.Get<Medewerker>(id)..Voornaam = DateTime.Now.ToString();
ts.Complete();

Fabio Maulo

unread,
Jan 15, 2010, 8:50:42 AM1/15/10
to nhu...@googlegroups.com
Did you see SQL ?
Did you see a SELECT in the second session.Get ?

2010/1/15 HH <hwhu...@gmail.com>
To unsubscribe from this group, send email to nhusers+u...@googlegroups.com.

For more options, visit this group at http://groups.google.com/group/nhusers?hl=en.






--
Fabio Maulo

HH

unread,
Jan 15, 2010, 9:20:22 AM1/15/10
to nhusers
Below the output from NHProf:

-- statement #1
enlisted session in distributed transaction with isolation level:
Serializable

-- statement #2
begin transaction with isolation level: Unspecified

-- statement #3
SELECT .......

-- statement #4
UPDATE ......

-- statement #5
commit transaction

-- statement #6
rollback transaction

-- statement #7
begin transaction with isolation level: Unspecified

-- statement #8
commit transaction

No select between statment #7 and #8.

@Jason: what if I opened a session before TS? Will disconnecting en
reconnecting the session register it with the TS?

On 15 jan, 14:50, Fabio Maulo <fabioma...@gmail.com> wrote:
> Did you see SQL ?
> Did you see a SELECT in the second session.Get ?
>

> 2010/1/15 HH <hwhuis...@gmail.com>

> > <nhusers%2Bunsu...@googlegroups.com<nhusers%252Bunsubscribe@googlegroup­s.com>

Fabio Maulo

unread,
Jan 15, 2010, 12:35:05 PM1/15/10
to nhu...@googlegroups.com
2010/1/15 HH <hwhu...@gmail.com>


-- statement #7
begin transaction with isolation level: Unspecified

-- statement #8
commit transaction

No select between statment #7 and #8.


Now you know why your test is not working.
In the second session.Get NH will Get the instance from session-cache and not from DB.

--
Fabio Maulo

HH

unread,
Jan 15, 2010, 1:17:19 PM1/15/10
to nhusers
Hi Fabio,

I don't want to sound rude, but I know that the get is comming from
the cache. I am not sure what you are trying to say, but isn't it
strange that NHibernate returns data from a cache based on a
transaction which was rollbacked? Or should I manage the NHibernate
cache by calling Clear() on the ISession?

I find it very hard to find out to which extend NHibernate supports
working with TS and where I should manage things myself.

Henk


On 15 jan, 18:35, Fabio Maulo <fabioma...@gmail.com> wrote:
> 2010/1/15 HH <hwhuis...@gmail.com>

Jason Meckley

unread,
Jan 15, 2010, 2:27:05 PM1/15/10
to nhusers
usually transactions are rolled back when an error is thrown. if an
error is thrown the session should be disposed of and a new one
opened. in the test case an exception is not thrown, the transaction
is rolled back and the session is not replaced. the session is holding
the entity in the identity map with the updated value. so the second
Get<> call is returning the item in the identity map, not the
database.


var value = "new text";


using(var ts = new TransactionScope())
using(var session = factory.OpenSession())
{

session.Get<Entity>(id).Property = value;
//rolling back
}

using(var ts = new TransactionScope())
using(var session = factory.OpenSession())
{

var property = session.Get<Entity>(id).Property;
Assert.That(property).Does.Not.Equal(value);

Fabio Maulo

unread,
Jan 15, 2010, 3:15:20 PM1/15/10
to nhu...@googlegroups.com
[DoItInAmbientTransaction]
[ItInvolvePersistence]
public void DoSomethingInControler()
{
   //Doing something in a request
}

When the method end its execution in general the session is closed (because exception or not it should be closed).
During its execution you are inside a UnitOfWork where the session-cache have place.

When I said "you can open the session outside the TS", mean that you *can* and not "you must".

Depending on your taste and how you are working with NH's session and NH's Transaction you can choose different implementation.

A Test should recreate/emulate a real-situation.
If I want pass your test I should say : "NH should't implements UoW and hit DB for each Get".
Instead Get you can use Refresh if you really need a fresh state of an entity.

If you want another example
[DoItInAmbientTransaction]
public void DoSomethingInControler()
{
  service.MakeSomething();
  SendAnEMail();
}

inside serivice
[NeedPeristence]
public void MakeSomething()
{
}

If SendAnMail fail with an exception the AmbientTransaction is rolled back so the NH's transaction is rolled back.
And I can continue because there are a lot of interpretations of MVC and DDD.

Try to explain how you are working and what you need or create a test recreating/emulating a real-situation and perhaps our answers may will be more concrete.
For example... what you are trying to emulate/recreate with that second session.Get ?
If your intention is "check the state of the DB after TS-Abort"do it but not using the same session. If you use the same session what is wrong is the test and the concept of UoW.

P.S. "rude" is something I can't understand at work.

2010/1/15 HH <hwhu...@gmail.com>
--
You received this message because you are subscribed to the Google Groups "nhusers" group.
To post to this group, send email to nhu...@googlegroups.com.
To unsubscribe from this group, send email to nhusers+u...@googlegroups.com.

For more options, visit this group at http://groups.google.com/group/nhusers?hl=en.






--
Fabio Maulo

Carlos cubas

unread,
Jan 15, 2010, 12:07:40 AM1/15/10
to nhu...@googlegroups.com
Fabio,

May I pick your brain regarding the usage of  Session.BeginTransaction() as an enlistment into the ambient transaction.

I've seen this question get asked more than a couple of times on the users group. So I just want to understand the reasoning for the decision to require the management of the NHTransaction & the Ambient transaction (TransactionScope).

I believe the question arises because it is a bit counter intuitive to manage both transactions as most other systems that implement System.Transaction, do so without the need to manage their older transaction api, or solely support the System.Transaction api (The only api I can think of right now is ado.net's IDbTransaction, but I remember seeing something around file system)

Thanks,

-Carlos
 
Practice makes perfect, but if no one is perfect, why practice?





Date: Thu, 14 Jan 2010 20:35:56 -0300
Subject: Re: [nhusers] Re: Nested TransactionScope with RequiresNew: unexpected behavior
From: fabio...@gmail.com
To: nhu...@googlegroups.com
Reply all
Reply to author
Forward
0 new messages