Skip to content

Commit 2ddb025

Browse files
NH-2176 - explicit connection enlistment.
1 parent d184a6e commit 2ddb025

File tree

1 file changed

+48
-9
lines changed
  • src/NHibernate.Test/NHSpecificTest/NH2176

1 file changed

+48
-9
lines changed

src/NHibernate.Test/NHSpecificTest/NH2176/Fixture.cs

Lines changed: 48 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
using System;
22
using System.Collections;
3+
using System.Collections.Concurrent;
4+
using System.Data.Common;
35
using System.Transactions;
46
using NHibernate.Cfg;
7+
using NHibernate.Dialect;
58
using NHibernate.Engine;
69
using NHibernate.Engine.Transaction;
710
using NHibernate.Transaction;
@@ -23,12 +26,12 @@ protected override void OnSetUp()
2326
using (var s = OpenSession())
2427
using (var tx = s.BeginTransaction())
2528
{
26-
var steve = new Person {Name = "Steve"};
27-
var peter = new Person {Name = "Peter"};
28-
var simon = new Person {Name = "Simon"};
29-
var paul = new Person {Name = "Paul"};
30-
var john = new Person {Name = "John"};
31-
var eric = new Person {Name = "Eric"};
29+
var steve = new Person { Name = "Steve" };
30+
var peter = new Person { Name = "Peter" };
31+
var simon = new Person { Name = "Simon" };
32+
var paul = new Person { Name = "Paul" };
33+
var john = new Person { Name = "John" };
34+
var eric = new Person { Name = "Eric" };
3235

3336
s.Save(steve);
3437
s.Save(peter);
@@ -75,8 +78,8 @@ public void MultipleConsecutiveTransactionScopesCanBeUsedInsideASingleSession()
7578
scope.Complete();
7679
}
7780

78-
// The exeption is caused by a race condition between two threads.
79-
// This can be demonstracted by uncommenting the following line which
81+
// The exception is caused by a race condition between two threads.
82+
// This can be demonstrated by uncommenting the following line which
8083
// causes the test to run without an exception.
8184
//System.Threading.Thread.Sleep(1000);
8285
}
@@ -90,6 +93,9 @@ public class CustomAdoNetTransactionFactory : ITransactionFactory
9093
private readonly AdoNetTransactionFactory _adoNetTransactionFactory =
9194
new AdoNetTransactionFactory();
9295

96+
private readonly ConcurrentDictionary<DbConnection, System.Transactions.Transaction> _sessionsTransaction =
97+
new ConcurrentDictionary<DbConnection, System.Transactions.Transaction>();
98+
9399
public void Configure(IDictionary props) { }
94100

95101
public ITransaction CreateTransaction(ISessionImplementor session)
@@ -99,8 +105,41 @@ public ITransaction CreateTransaction(ISessionImplementor session)
99105

100106
public void EnlistInDistributedTransactionIfNeeded(ISessionImplementor session)
101107
{
102-
// No enlistment. This disables automatic flushes before ambient transaction
108+
// No session enlistment. This disables automatic flushes before ambient transaction
103109
// commits. Explicit Flush calls required.
110+
// Still make sure the session connection is enlisted, in case it was acquired before
111+
// transaction scope start.
112+
// Will not support nested transaction scope. (Will throw, while current NHibernate
113+
// just stay in previous scope.)
114+
// Will cause an "earlier than required" connection acquisition.
115+
// It is required to enlist with null when the scope is ended, otherwise using
116+
// the transaction without a new scope will fail by attempting to use it inside
117+
// the completed scope.
118+
// If an explicit transaction is ongoing, we must not enlist.
119+
if (!session.ConnectionManager.Transaction.IsActive)
120+
{
121+
// Enlist is called terribly frequently, and in some circumstances, it will
122+
// not support to be called with the same value. So track what was the previous
123+
// call and do not call it again if unneeded.
124+
// (And Sql/OleDb/Odbc/Oracle manage/PostgreSql/MySql/Firebird connections support
125+
// multiple calls with the same ongoing transaction, but some others may not.)
126+
var current = System.Transactions.Transaction.Current;
127+
var connection = session.Connection;
128+
System.Transactions.Transaction previous;
129+
if (!_sessionsTransaction.TryGetValue(connection, out previous) || previous != current)
130+
{
131+
_sessionsTransaction.AddOrUpdate(connection, current, (s, t) => current);
132+
if (current == null &&
133+
(session.Factory.Dialect is SQLiteDialect || session.Factory.Dialect is MsSqlCeDialect))
134+
{
135+
// Some connections does not support enlisting with null
136+
// Let them with their previous transaction if any, the application
137+
// will fail if the connection was left with a completed transaction due to this.
138+
return;
139+
}
140+
session.Connection.EnlistTransaction(System.Transactions.Transaction.Current);
141+
}
142+
}
104143
}
105144

106145
public bool IsInDistributedActiveTransaction(ISessionImplementor session)

0 commit comments

Comments
 (0)