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,44 @@ 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. We should not enlist
119
+ // either if the connection was supplied by user (let him handle that in such case),
120
+ // but there are currently no ways to know this from here.
121
+ if ( ! session . ConnectionManager . Transaction . IsActive )
122
+ {
123
+ // Enlist is called terribly frequently, and in some circumstances, it will
124
+ // not support to be called with the same value. So track what was the previous
125
+ // call and do not call it again if unneeded.
126
+ // (And Sql/OleDb/Odbc/Oracle manage/PostgreSql/MySql/Firebird/SQLite connections
127
+ // support multiple calls with the same ongoing transaction, but some others may not.)
128
+ var current = System . Transactions . Transaction . Current ;
129
+ var connection = session . Connection ;
130
+ System . Transactions . Transaction previous ;
131
+ if ( ! _sessionsTransaction . TryGetValue ( connection , out previous ) || previous != current )
132
+ {
133
+ _sessionsTransaction . AddOrUpdate ( connection , current , ( s , t ) => current ) ;
134
+ if ( current == null &&
135
+ // This will need an ad-hoc property on Dialect base class instead.
136
+ ( session . Factory . Dialect is SQLiteDialect || session . Factory . Dialect is MsSqlCeDialect ) )
137
+ {
138
+ // Some connections does not support enlisting with null
139
+ // Let them with their previous transaction if any, the application
140
+ // will fail if the connection was left with a completed transaction due to this.
141
+ return ;
142
+ }
143
+ session . Connection . EnlistTransaction ( System . Transactions . Transaction . Current ) ;
144
+ }
145
+ }
104
146
}
105
147
106
148
public bool IsInDistributedActiveTransaction ( ISessionImplementor session )
0 commit comments