1
1
using System ;
2
2
using System . Collections ;
3
+ using System . Collections . Concurrent ;
4
+ using System . Data . Common ;
3
5
using System . Transactions ;
4
6
using NHibernate . Cfg ;
7
+ using NHibernate . Dialect ;
5
8
using NHibernate . Engine ;
6
9
using NHibernate . Engine . Transaction ;
7
10
using NHibernate . Transaction ;
@@ -23,12 +26,12 @@ protected override void OnSetUp()
23
26
using ( var s = OpenSession ( ) )
24
27
using ( var tx = s . BeginTransaction ( ) )
25
28
{
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" } ;
32
35
33
36
s . Save ( steve ) ;
34
37
s . Save ( peter ) ;
@@ -75,8 +78,8 @@ public void MultipleConsecutiveTransactionScopesCanBeUsedInsideASingleSession()
75
78
scope . Complete ( ) ;
76
79
}
77
80
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
80
83
// causes the test to run without an exception.
81
84
//System.Threading.Thread.Sleep(1000);
82
85
}
@@ -90,6 +93,9 @@ public class CustomAdoNetTransactionFactory : ITransactionFactory
90
93
private readonly AdoNetTransactionFactory _adoNetTransactionFactory =
91
94
new AdoNetTransactionFactory ( ) ;
92
95
96
+ private readonly ConcurrentDictionary < DbConnection , System . Transactions . Transaction > _sessionsTransaction =
97
+ new ConcurrentDictionary < DbConnection , System . Transactions . Transaction > ( ) ;
98
+
93
99
public void Configure ( IDictionary props ) { }
94
100
95
101
public ITransaction CreateTransaction ( ISessionImplementor session )
@@ -99,8 +105,41 @@ public ITransaction CreateTransaction(ISessionImplementor session)
99
105
100
106
public void EnlistInDistributedTransactionIfNeeded ( ISessionImplementor session )
101
107
{
102
- // No enlistment. This disables automatic flushes before ambient transaction
108
+ // No session enlistment. This disables automatic flushes before ambient transaction
103
109
// 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
+ }
104
143
}
105
144
106
145
public bool IsInDistributedActiveTransaction ( ISessionImplementor session )
0 commit comments