Skip to content

Commit f9c2a3a

Browse files
NH-4018 - Option for enlisting in system transaction.
1 parent bae7bd4 commit f9c2a3a

25 files changed

+271
-45
lines changed

src/NHibernate.Test/Async/NHSpecificTest/NH1632/Fixture.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,10 +100,11 @@ public async Task When_commiting_items_in_DTC_transaction_will_add_items_to_2nd_
100100
//closing the connection to ensure we can't really use it.
101101
var connection = await (Sfi.ConnectionProvider.GetConnectionAsync(CancellationToken.None));
102102
Sfi.ConnectionProvider.CloseConnection(connection);
103+
103104
// The session is supposed to succeed because the second level cache should have the
104105
// entity to load, allowing the session to not use the connection at all.
105-
// Will fail if transaction manager tries to enlist user supplied connection, due
106-
// to using a transaction scope below.
106+
// Will fail if a transaction manager tries to enlist user supplied connection. Do
107+
// not add a transaction scope below.
107108
using (var s = Sfi.WithOptions().Connection(connection).OpenSession())
108109
{
109110
Nums nums = null;

src/NHibernate.Test/Async/SystemTransactions/TransactionNotificationFixture.cs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,23 +8,19 @@
88
//------------------------------------------------------------------------------
99

1010

11-
using System;
1211
using System.Collections;
13-
using System.Data.Common;
14-
using System.Threading;
1512
using System.Transactions;
1613
using NUnit.Framework;
1714

1815
namespace NHibernate.Test.SystemTransactions
1916
{
2017
using System.Threading.Tasks;
18+
using System.Threading;
2119
[TestFixture]
2220
public class TransactionNotificationFixtureAsync : TestCase
2321
{
2422
protected override IList Mappings
25-
{
26-
get { return new string[] {}; }
27-
}
23+
=> new string[] { };
2824

2925
[Test]
3026
public async Task TwoTransactionScopesInsideOneSessionAsync()

src/NHibernate.Test/DebugSessionFactory.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,12 @@ ISessionBuilder ISessionBuilder<ISessionBuilder>.AutoClose(bool autoClose)
441441
return this;
442442
}
443443

444+
ISessionBuilder ISessionBuilder<ISessionBuilder>.AutoJoinTransaction(bool autoJoinTransaction)
445+
{
446+
_actualBuilder.AutoJoinTransaction(autoJoinTransaction);
447+
return this;
448+
}
449+
444450
ISessionBuilder ISessionBuilder<ISessionBuilder>.FlushMode(FlushMode flushMode)
445451
{
446452
_actualBuilder.FlushMode(flushMode);
@@ -478,6 +484,12 @@ IStatelessSessionBuilder IStatelessSessionBuilder.Connection(DbConnection connec
478484
return this;
479485
}
480486

487+
IStatelessSessionBuilder IStatelessSessionBuilder.AutoJoinTransaction(bool autoJoinTransaction)
488+
{
489+
_actualBuilder.AutoJoinTransaction(autoJoinTransaction);
490+
return this;
491+
}
492+
481493
#endregion
482494
}
483495
}

src/NHibernate.Test/NHSpecificTest/NH1054/DummyTransactionFactory.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ public void EnlistInSystemTransactionIfNeeded(ISessionImplementor session)
2222
throw new NotImplementedException();
2323
}
2424

25+
public void ExplicitJoinSystemTransaction(ISessionImplementor session)
26+
{
27+
throw new NotImplementedException();
28+
}
29+
2530
public bool IsInActiveSystemTransaction(ISessionImplementor session)
2631
{
2732
return false;

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,10 +105,11 @@ public void When_commiting_items_in_DTC_transaction_will_add_items_to_2nd_level_
105105
//closing the connection to ensure we can't really use it.
106106
var connection = Sfi.ConnectionProvider.GetConnection();
107107
Sfi.ConnectionProvider.CloseConnection(connection);
108+
108109
// The session is supposed to succeed because the second level cache should have the
109110
// entity to load, allowing the session to not use the connection at all.
110-
// Will fail if transaction manager tries to enlist user supplied connection, due
111-
// to using a transaction scope below.
111+
// Will fail if a transaction manager tries to enlist user supplied connection. Do
112+
// not add a transaction scope below.
112113
using (var s = Sfi.WithOptions().Connection(connection).OpenSession())
113114
{
114115
Nums nums = null;

src/NHibernate.Test/SessionBuilder/Fixture.cs

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ public class Fixture : TestCase
1111
{
1212
protected override string MappingsAssembly => "NHibernate.Test";
1313

14-
protected override IList Mappings => new [] { "SessionBuilder.Mappings.hbm.xml" };
14+
protected override IList Mappings => new[] { "SessionBuilder.Mappings.hbm.xml" };
1515

1616
protected override void Configure(Configuration configuration)
1717
{
@@ -35,12 +35,51 @@ private void CanSetAutoClose<T>(T sb) where T : ISessionBuilder<T>
3535
var options = DebugSessionFactory.GetCreationOptions(sb);
3636
CanSet(sb, sb.AutoClose, () => options.ShouldAutoClose,
3737
sb is ISharedSessionBuilder ssb ? ssb.AutoClose : default(Func<ISharedSessionBuilder>),
38-
// initial values
38+
// initial value
3939
false,
4040
// values
4141
true, false);
4242
}
4343

44+
[Test]
45+
public void CanSetAutoJoinTransaction()
46+
{
47+
var sb = Sfi.WithOptions();
48+
CanSetAutoJoinTransaction(sb);
49+
using (var s = sb.OpenSession())
50+
{
51+
CanSetAutoJoinTransaction(s.SessionWithOptions());
52+
}
53+
}
54+
55+
private void CanSetAutoJoinTransaction<T>(T sb) where T : ISessionBuilder<T>
56+
{
57+
var options = DebugSessionFactory.GetCreationOptions(sb);
58+
CanSet(sb, sb.AutoJoinTransaction, () => options.ShouldAutoJoinTransaction,
59+
sb is ISharedSessionBuilder ssb ? ssb.AutoJoinTransaction : default(Func<ISharedSessionBuilder>),
60+
// initial value
61+
true,
62+
// values
63+
false, true);
64+
}
65+
66+
[Test]
67+
public void CanSetAutoJoinTransactionOnStateless()
68+
{
69+
var sb = Sfi.WithStatelessOptions();
70+
71+
var sbType = sb.GetType().Name;
72+
var options = DebugSessionFactory.GetCreationOptions(sb);
73+
Assert.That(options.ShouldAutoJoinTransaction, Is.True, $"{sbType}: Initial value");
74+
var fsb = sb.AutoJoinTransaction(false);
75+
Assert.That(options.ShouldAutoJoinTransaction, Is.False, $"{sbType}: After call with false");
76+
Assert.That(fsb, Is.SameAs(sb), $"{sbType}: Unexpected fluent return after call with false");
77+
78+
fsb = sb.AutoJoinTransaction(true);
79+
Assert.That(options.ShouldAutoJoinTransaction, Is.True, $"{sbType}: After call with true");
80+
Assert.That(fsb, Is.SameAs(sb), $"{sbType}: Unexpected fluent return after call with true");
81+
}
82+
4483
[Test]
4584
public void CanSetConnection()
4685
{
@@ -136,7 +175,7 @@ private void CanSetConnectionReleaseMode<T>(T sb) where T : ISessionBuilder<T>
136175
var options = DebugSessionFactory.GetCreationOptions(sb);
137176
CanSet(sb, sb.ConnectionReleaseMode, () => options.SessionConnectionReleaseMode,
138177
sb is ISharedSessionBuilder ssb ? ssb.ConnectionReleaseMode : default(Func<ISharedSessionBuilder>),
139-
// initial values
178+
// initial value
140179
Sfi.Settings.ConnectionReleaseMode,
141180
// values
142181
ConnectionReleaseMode.OnClose, ConnectionReleaseMode.AfterStatement, ConnectionReleaseMode.AfterTransaction);
@@ -158,7 +197,7 @@ private void CanSetFlushMode<T>(T sb) where T : ISessionBuilder<T>
158197
var options = DebugSessionFactory.GetCreationOptions(sb);
159198
CanSet(sb, sb.FlushMode, () => options.InitialSessionFlushMode,
160199
sb is ISharedSessionBuilder ssb ? ssb.FlushMode : default(Func<ISharedSessionBuilder>),
161-
// initial values
200+
// initial value
162201
Sfi.Settings.DefaultFlushMode,
163202
// values
164203
FlushMode.Always, FlushMode.Auto, FlushMode.Commit, FlushMode.Manual);

src/NHibernate.Test/SystemTransactions/TransactionNotificationFixture.cs

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
1-
using System;
21
using System.Collections;
3-
using System.Data.Common;
4-
using System.Threading;
52
using System.Transactions;
63
using NUnit.Framework;
74

@@ -11,28 +8,37 @@ namespace NHibernate.Test.SystemTransactions
118
public class TransactionNotificationFixture : TestCase
129
{
1310
protected override IList Mappings
11+
=> new string[] { };
12+
13+
[Test]
14+
public void NoTransaction()
1415
{
15-
get { return new string[] {}; }
16+
var interceptor = new RecordingInterceptor();
17+
using (Sfi.WithOptions().Interceptor(interceptor).OpenSession()) { }
18+
Assert.AreEqual(0, interceptor.afterTransactionBeginCalled);
19+
Assert.AreEqual(0, interceptor.beforeTransactionCompletionCalled);
20+
Assert.AreEqual(0, interceptor.afterTransactionCompletionCalled);
1621
}
1722

18-
1923
[Test]
20-
public void NoTransaction()
24+
public void TransactionDisabled()
2125
{
2226
var interceptor = new RecordingInterceptor();
23-
using (Sfi.WithOptions().Interceptor(interceptor).OpenSession())
27+
using (var ts = new TransactionScope())
28+
using (Sfi.WithOptions().Interceptor(interceptor).AutoJoinTransaction(false).OpenSession())
2429
{
25-
Assert.AreEqual(0, interceptor.afterTransactionBeginCalled);
26-
Assert.AreEqual(0, interceptor.beforeTransactionCompletionCalled);
27-
Assert.AreEqual(0, interceptor.afterTransactionCompletionCalled);
30+
ts.Complete();
2831
}
32+
Assert.AreEqual(0, interceptor.afterTransactionBeginCalled);
33+
Assert.AreEqual(0, interceptor.beforeTransactionCompletionCalled);
34+
Assert.AreEqual(0, interceptor.afterTransactionCompletionCalled);
2935
}
3036

3137
[Test]
3238
public void AfterBegin()
3339
{
3440
var interceptor = new RecordingInterceptor();
35-
using (new TransactionScope())
41+
using (new TransactionScope())
3642
using (Sfi.WithOptions().Interceptor(interceptor).OpenSession())
3743
{
3844
Assert.AreEqual(1, interceptor.afterTransactionBeginCalled);
@@ -46,15 +52,15 @@ public void Complete()
4652
{
4753
var interceptor = new RecordingInterceptor();
4854
ISession session;
49-
using(var scope = new TransactionScope())
55+
using (var scope = new TransactionScope())
5056
{
5157
session = Sfi.WithOptions().Interceptor(interceptor).OpenSession();
5258
scope.Complete();
5359
}
5460
session.Dispose();
5561
Assert.AreEqual(1, interceptor.beforeTransactionCompletionCalled);
5662
Assert.AreEqual(1, interceptor.afterTransactionCompletionCalled);
57-
63+
5864
}
5965

6066
[Test]

src/NHibernate/AdoNet/ConnectionManager.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,8 @@ public ConnectionManager(
6969
ISessionImplementor session,
7070
DbConnection suppliedConnection,
7171
ConnectionReleaseMode connectionReleaseMode,
72-
IInterceptor interceptor)
72+
IInterceptor interceptor,
73+
bool shouldAutoJoinTransaction)
7374
{
7475
Session = session;
7576
_connection = suppliedConnection;
@@ -79,6 +80,7 @@ public ConnectionManager(
7980
_batcher = session.Factory.Settings.BatcherFactory.CreateBatcher(this, interceptor);
8081

8182
_ownConnection = suppliedConnection == null;
83+
ShouldAutoJoinTransaction = shouldAutoJoinTransaction;
8284
}
8385

8486
public void AddDependentSession(ISessionImplementor session)
@@ -100,6 +102,8 @@ public bool IsInActiveTransaction
100102
public bool IsConnected
101103
=> _connection != null || _ownConnection;
102104

105+
public bool ShouldAutoJoinTransaction { get; }
106+
103107
public void Reconnect()
104108
{
105109
if (IsConnected)
@@ -122,7 +126,7 @@ public void Reconnect(DbConnection suppliedConnection)
122126
_ownConnection = false;
123127

124128
// May fail if the supplied connection is enlisted in another transaction, which would be an user
125-
// error.
129+
// error. (Either disable auto join transaction or supply an enlist-able connection.)
126130
if (_currentSystemTransaction != null)
127131
_connection.EnlistTransaction(_currentSystemTransaction);
128132
}

src/NHibernate/Async/Transaction/ITransactionFactory.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
//------------------------------------------------------------------------------
99

1010

11+
using System;
1112
using System.Collections.Generic;
1213
using NHibernate.Engine;
1314
using NHibernate.Engine.Transaction;

src/NHibernate/Dialect/FirebirdDialect.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -555,7 +555,8 @@ private static bool IsUnallowedDecimal(DbType dbType, int precision)
555555
/// Does this dialect support distributed transaction?
556556
/// </summary>
557557
/// <remarks>
558-
/// As of v2.5, fails rollback-ing changes when distributed: changes are instead persisted in database.
558+
/// As of v2.5 and 3.0.2, fails rollback-ing changes when distributed: changes are instead persisted in database.
559+
/// (With ADO .Net Provider 5.9.1)
559560
/// </remarks>
560561
public override bool SupportsDistributedTransactions => false;
561562

src/NHibernate/Engine/ISessionImplementor.cs

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -266,20 +266,23 @@ public partial interface ISessionImplementor
266266

267267
IQuery GetNamedQuery(string queryName);
268268

269-
/// <summary> Determine whether the session is closed. Provided separately from
270-
/// {@link #isOpen()} as this method does not attempt any JTA sync
271-
/// registration, where as {@link #isOpen()} does; which makes this one
272-
/// nicer to use for most internal purposes.
269+
/// <summary>
270+
/// Determine whether the session is closed. Provided separately from
271+
/// <c>IsOpen</c> as this method does not attempt any system transaction sync
272+
/// registration, whereas <c>IsOpen</c> is allowed to (does not currently, but may do
273+
/// in a future version as it is the case in Hibernate); which makes this one
274+
/// nicer to use for most internal purposes.
273275
/// </summary>
274-
/// <returns> True if the session is closed; false otherwise.
276+
/// <returns>
277+
/// <see langword="true" /> if the session is closed; <see langword="false" /> otherwise.
275278
/// </returns>
276279
bool IsClosed { get; }
277280

278281
void Flush();
279282

280-
/// <summary>
281-
/// Does this <tt>Session</tt> have an active Hibernate transaction
282-
/// or is there a JTA transaction in progress?
283+
/// <summary>
284+
/// Does this <c>ISession</c> have an active NHibernate transaction
285+
/// or is there a system transaction in progress in which the session is enlisted?
283286
/// </summary>
284287
bool TransactionInProgress { get; }
285288

@@ -297,6 +300,23 @@ public partial interface ISessionImplementor
297300

298301
ITransactionContext TransactionContext { get; set; }
299302

303+
/// <summary>
304+
/// Join the <see cref="System.Transactions.Transaction.Current"/> system transaction.
305+
/// </summary>
306+
/// <remarks>
307+
/// <para>
308+
/// Sessions auto-join current transaction by default on their first usage within a scope.
309+
/// This can be disabled with <see cref="ISessionBuilder{T}.AutoJoinTransaction(bool)"/> from
310+
/// a session builder obtained with <see cref="ISessionFactory.WithOptions()"/>.
311+
/// </para>
312+
/// <para>
313+
/// This method allows to explicitly join the current transaction. It does nothing if it is already
314+
/// joined.
315+
/// </para>
316+
/// </remarks>
317+
/// <exception cref="HibernateException">Thrown if there is no current transaction.</exception>
318+
void JoinTransaction();
319+
300320
void CloseSessionFromSystemTransaction();
301321

302322
EntityKey GenerateEntityKey(object id, IEntityPersister persister);

src/NHibernate/ISession.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -707,6 +707,23 @@ public partial interface ISession : IDisposable
707707
/// </summary>
708708
ITransaction Transaction { get; }
709709

710+
/// <summary>
711+
/// Join the <see cref="System.Transactions.Transaction.Current"/> system transaction.
712+
/// </summary>
713+
/// <remarks>
714+
/// <para>
715+
/// Sessions auto-join current transaction by default on their first usage within a scope.
716+
/// This can be disabled with <see cref="ISessionBuilder{T}.AutoJoinTransaction(bool)"/> from
717+
/// a session builder obtained with <see cref="ISessionFactory.WithOptions()"/>.
718+
/// </para>
719+
/// <para>
720+
/// This method allows to explicitly join the current transaction. It does nothing if it is already
721+
/// joined.
722+
/// </para>
723+
/// </remarks>
724+
/// <exception cref="HibernateException">Thrown if there is no current transaction.</exception>
725+
void JoinTransaction();
726+
710727
/// <summary>
711728
/// Creates a new <c>Criteria</c> for the entity class.
712729
/// </summary>

src/NHibernate/ISessionBuilder.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,16 @@ public interface ISessionBuilder<T> where T : ISessionBuilder<T>
6565
/// <returns><see langword="this" />, for method chaining.</returns>
6666
T AutoClose(bool autoClose);
6767

68+
/// <summary>
69+
/// Should the session be automatically enlisted in ambient system transaction?
70+
/// Enabled by default. Disabling it does not prevent connections having auto-enlistment
71+
/// enabled to get enlisted in current ambient transaction when opened.
72+
/// </summary>
73+
/// <param name="autoJoinTransaction">Should the session be automatically explicitly
74+
/// enlisted in ambient transaction.</param>
75+
/// <returns><see langword="this" />, for method chaining.</returns>
76+
T AutoJoinTransaction(bool autoJoinTransaction);
77+
6878
/// <summary>
6979
/// Specify the initial FlushMode to use for the opened Session.
7080
/// </summary>

0 commit comments

Comments
 (0)