diff --git a/src/NHibernate.Test/Async/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs b/src/NHibernate.Test/Async/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs index 3fc06fb5a44..81f6b14b5cd 100644 --- a/src/NHibernate.Test/Async/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs +++ b/src/NHibernate.Test/Async/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs @@ -19,6 +19,7 @@ using NHibernate.Dialect; using NHibernate.Driver; using NHibernate.Engine; +using NHibernate.Engine.Transaction; using NHibernate.Linq; using NHibernate.Mapping.ByCode; using NHibernate.MultiTenancy; @@ -28,6 +29,7 @@ namespace NHibernate.Test.MultiTenancy { using System.Threading.Tasks; + using System.Threading; [TestFixture] public class DatabaseStrategyNoDbSpecificFixtureAsync : TestCaseMappingByCode { @@ -160,6 +162,18 @@ public async Task TenantSessionIsSerializableAndCanBeReconnectedAsync() Assert.That(Sfi.Statistics.SecondLevelCacheHitCount, Is.EqualTo(1)); } + [Test] + public async Task TenantIsolatedWorkOpensTenantConnectionAsync() + { + if (!IsSqlServerDialect) + Assert.Ignore("MSSqlServer specific test"); + + using (var ses = OpenTenantSession("tenant1")) + { + await (Isolater.DoIsolatedWorkAsync(new TenantIsolatatedWork("tenant1"), ses.GetSessionImplementation(), CancellationToken.None)); + } + } + private static string GetTenantId(ISession session) { return session.GetSessionImplementation().GetTenantIdentifier(); diff --git a/src/NHibernate.Test/Async/MultiTenancy/TenantIsolatatedWork.cs b/src/NHibernate.Test/Async/MultiTenancy/TenantIsolatatedWork.cs new file mode 100644 index 00000000000..238f854e7ed --- /dev/null +++ b/src/NHibernate.Test/Async/MultiTenancy/TenantIsolatatedWork.cs @@ -0,0 +1,30 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +using System.Data.Common; +using System.Data.SqlClient; +using NHibernate.Engine.Transaction; + +namespace NHibernate.Test.MultiTenancy +{ + using System.Threading.Tasks; + using System.Threading; + public partial class TenantIsolatatedWork : IIsolatedWork + { + + public Task DoWorkAsync(DbConnection connection, DbTransaction transaction, CancellationToken cancellationToken) + { + var builder = new SqlConnectionStringBuilder(connection.ConnectionString); + if (builder.ApplicationName != _tenantName) + return Task.FromException(new HibernateException("Invalid tenant connection")); + return Task.CompletedTask; + } + } +} diff --git a/src/NHibernate.Test/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs b/src/NHibernate.Test/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs index 2a37052266b..fcd93d71595 100644 --- a/src/NHibernate.Test/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs +++ b/src/NHibernate.Test/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs @@ -9,6 +9,7 @@ using NHibernate.Dialect; using NHibernate.Driver; using NHibernate.Engine; +using NHibernate.Engine.Transaction; using NHibernate.Linq; using NHibernate.Mapping.ByCode; using NHibernate.MultiTenancy; @@ -229,6 +230,18 @@ public void TenantSessionIsSerializableAndCanBeReconnected() Assert.That(Sfi.Statistics.SecondLevelCacheHitCount, Is.EqualTo(1)); } + [Test] + public void TenantIsolatedWorkOpensTenantConnection() + { + if (!IsSqlServerDialect) + Assert.Ignore("MSSqlServer specific test"); + + using (var ses = OpenTenantSession("tenant1")) + { + Isolater.DoIsolatedWork(new TenantIsolatatedWork("tenant1"), ses.GetSessionImplementation()); + } + } + private static string GetTenantId(ISession session) { return session.GetSessionImplementation().GetTenantIdentifier(); diff --git a/src/NHibernate.Test/MultiTenancy/TenantIsolatatedWork.cs b/src/NHibernate.Test/MultiTenancy/TenantIsolatatedWork.cs new file mode 100644 index 00000000000..129fcc0e684 --- /dev/null +++ b/src/NHibernate.Test/MultiTenancy/TenantIsolatatedWork.cs @@ -0,0 +1,23 @@ +using System.Data.Common; +using System.Data.SqlClient; +using NHibernate.Engine.Transaction; + +namespace NHibernate.Test.MultiTenancy +{ + public partial class TenantIsolatatedWork : IIsolatedWork + { + private readonly string _tenantName; + + public TenantIsolatatedWork(string tenantName) + { + _tenantName = tenantName; + } + + public void DoWork(DbConnection connection, DbTransaction transaction) + { + var builder = new SqlConnectionStringBuilder(connection.ConnectionString); + if (builder.ApplicationName != _tenantName) + throw new HibernateException("Invalid tenant connection"); + } + } +} diff --git a/src/NHibernate/AdoNet/ConnectionManager.cs b/src/NHibernate/AdoNet/ConnectionManager.cs index 5ebf51a7fb1..882452b4d98 100644 --- a/src/NHibernate/AdoNet/ConnectionManager.cs +++ b/src/NHibernate/AdoNet/ConnectionManager.cs @@ -224,6 +224,19 @@ private void CloseConnection() _connection = null; } + /// + /// Get a new opened connection. The caller is responsible for closing it. + /// + /// An opened connection. + public DbConnection GetNewConnection() + { + return _connectionAccess.GetConnection(); + } + + /// + /// Get the managed connection. + /// + /// An opened connection. public DbConnection GetConnection() { if (!_allowConnectionUsage) @@ -254,7 +267,7 @@ public DbConnection GetConnection() { if (_ownConnection) { - _connection = _connectionAccess.GetConnection(); + _connection = GetNewConnection(); // Will fail if the connection is already enlisted in another transaction. // Probable case: nested transaction scope with connection auto-enlistment enabled. // That is an user error. diff --git a/src/NHibernate/Async/AdoNet/ConnectionManager.cs b/src/NHibernate/Async/AdoNet/ConnectionManager.cs index 90487416762..4032066ee0a 100644 --- a/src/NHibernate/Async/AdoNet/ConnectionManager.cs +++ b/src/NHibernate/Async/AdoNet/ConnectionManager.cs @@ -25,6 +25,25 @@ namespace NHibernate.AdoNet public partial class ConnectionManager : ISerializable, IDeserializationCallback { + /// + /// Get a new opened connection. The caller is responsible for closing it. + /// + /// A cancellation token that can be used to cancel the work + /// An opened connection. + public Task GetNewConnectionAsync(CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + return _connectionAccess.GetConnectionAsync(cancellationToken); + } + + /// + /// Get the managed connection. + /// + /// A cancellation token that can be used to cancel the work + /// An opened connection. public Task GetConnectionAsync(CancellationToken cancellationToken) { if (!_allowConnectionUsage) @@ -62,7 +81,7 @@ async Task InternalGetConnectionAsync() { if (_ownConnection) { - _connection = await (_connectionAccess.GetConnectionAsync(cancellationToken)).ConfigureAwait(false); + _connection = await (GetNewConnectionAsync(cancellationToken)).ConfigureAwait(false); // Will fail if the connection is already enlisted in another transaction. // Probable case: nested transaction scope with connection auto-enlistment enabled. // That is an user error. diff --git a/src/NHibernate/Async/Transaction/AdoNetTransactionFactory.cs b/src/NHibernate/Async/Transaction/AdoNetTransactionFactory.cs index d6d5c59925d..2fd5c3fa458 100644 --- a/src/NHibernate/Async/Transaction/AdoNetTransactionFactory.cs +++ b/src/NHibernate/Async/Transaction/AdoNetTransactionFactory.cs @@ -47,7 +47,7 @@ async Task InternalExecuteWorkInIsolationAsync() // since SQLite only allows one connection to the database. connection = session.Factory.Dialect is SQLiteDialect ? session.Connection - : await (session.Factory.ConnectionProvider.GetConnectionAsync(cancellationToken)).ConfigureAwait(false); + : await (session.ConnectionManager.GetNewConnectionAsync(cancellationToken)).ConfigureAwait(false); if (transacted) { diff --git a/src/NHibernate/Transaction/AdoNetTransactionFactory.cs b/src/NHibernate/Transaction/AdoNetTransactionFactory.cs index 5fe50f06757..750269baecc 100644 --- a/src/NHibernate/Transaction/AdoNetTransactionFactory.cs +++ b/src/NHibernate/Transaction/AdoNetTransactionFactory.cs @@ -57,7 +57,7 @@ public virtual void ExecuteWorkInIsolation(ISessionImplementor session, IIsolate // since SQLite only allows one connection to the database. connection = session.Factory.Dialect is SQLiteDialect ? session.Connection - : session.Factory.ConnectionProvider.GetConnection(); + : session.ConnectionManager.GetNewConnection(); if (transacted) {