From 2391724477e7ebb177c5e565def8c281c2388cd9 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Wed, 23 Jun 2021 07:19:36 +0300 Subject: [PATCH 1/4] Fix ExecuteWorkInIsolation ignores MultiTenancy configuration --- .../DatabaseStrategyNoDbSpecificFixture.cs | 14 +++++++ .../MultiTenancy/TenantIsolatatedWork.cs | 38 +++++++++++++++++++ .../DatabaseStrategyNoDbSpecificFixture.cs | 13 +++++++ .../MultiTenancy/TenantIsolatatedWork.cs | 24 ++++++++++++ src/NHibernate/AdoNet/ConnectionManager.cs | 7 +++- .../Async/AdoNet/ConnectionManager.cs | 11 +++++- .../Transaction/AdoNetTransactionFactory.cs | 2 +- .../Transaction/AdoNetTransactionFactory.cs | 2 +- 8 files changed, 107 insertions(+), 4 deletions(-) create mode 100644 src/NHibernate.Test/Async/MultiTenancy/TenantIsolatatedWork.cs create mode 100644 src/NHibernate.Test/MultiTenancy/TenantIsolatatedWork.cs 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..bd1c6a0d280 --- /dev/null +++ b/src/NHibernate.Test/Async/MultiTenancy/TenantIsolatatedWork.cs @@ -0,0 +1,38 @@ +//------------------------------------------------------------------------------ +// +// 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 Microsoft.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) + { + try + { + var con = (SqlConnection) connection; + var builder = new SqlConnectionStringBuilder(con.ConnectionString); + if (builder.ApplicationName != _tenantName) + return Task.FromException(new HibernateException("Invalid tenant connection")); + return Task.CompletedTask; + } + catch (System.Exception ex) + { + return Task.FromException(ex); + } + } + } +} 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..7b26eab7b35 --- /dev/null +++ b/src/NHibernate.Test/MultiTenancy/TenantIsolatatedWork.cs @@ -0,0 +1,24 @@ +using System.Data.Common; +using Microsoft.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 con = (SqlConnection) connection; + var builder = new SqlConnectionStringBuilder(con.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..8ea6cd62699 100644 --- a/src/NHibernate/AdoNet/ConnectionManager.cs +++ b/src/NHibernate/AdoNet/ConnectionManager.cs @@ -224,6 +224,11 @@ private void CloseConnection() _connection = null; } + public DbConnection GetNewConnection() + { + return _connectionAccess.GetConnection(); + } + public DbConnection GetConnection() { if (!_allowConnectionUsage) @@ -254,7 +259,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..0d93aecab15 100644 --- a/src/NHibernate/Async/AdoNet/ConnectionManager.cs +++ b/src/NHibernate/Async/AdoNet/ConnectionManager.cs @@ -25,6 +25,15 @@ namespace NHibernate.AdoNet public partial class ConnectionManager : ISerializable, IDeserializationCallback { + public Task GetNewConnectionAsync(CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + return _connectionAccess.GetConnectionAsync(cancellationToken); + } + public Task GetConnectionAsync(CancellationToken cancellationToken) { if (!_allowConnectionUsage) @@ -62,7 +71,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) { From f5212d7ca7db2708affde16da0609fb688ba9136 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Wed, 23 Jun 2021 07:36:39 +0300 Subject: [PATCH 2/4] Fix NetFx test --- .../Async/MultiTenancy/TenantIsolatatedWork.cs | 18 +++++------------- .../MultiTenancy/TenantIsolatatedWork.cs | 5 ++--- 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/src/NHibernate.Test/Async/MultiTenancy/TenantIsolatatedWork.cs b/src/NHibernate.Test/Async/MultiTenancy/TenantIsolatatedWork.cs index bd1c6a0d280..238f854e7ed 100644 --- a/src/NHibernate.Test/Async/MultiTenancy/TenantIsolatatedWork.cs +++ b/src/NHibernate.Test/Async/MultiTenancy/TenantIsolatatedWork.cs @@ -9,7 +9,7 @@ using System.Data.Common; -using Microsoft.Data.SqlClient; +using System.Data.SqlClient; using NHibernate.Engine.Transaction; namespace NHibernate.Test.MultiTenancy @@ -21,18 +21,10 @@ public partial class TenantIsolatatedWork : IIsolatedWork public Task DoWorkAsync(DbConnection connection, DbTransaction transaction, CancellationToken cancellationToken) { - try - { - var con = (SqlConnection) connection; - var builder = new SqlConnectionStringBuilder(con.ConnectionString); - if (builder.ApplicationName != _tenantName) - return Task.FromException(new HibernateException("Invalid tenant connection")); - return Task.CompletedTask; - } - catch (System.Exception ex) - { - return Task.FromException(ex); - } + 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/TenantIsolatatedWork.cs b/src/NHibernate.Test/MultiTenancy/TenantIsolatatedWork.cs index 7b26eab7b35..129fcc0e684 100644 --- a/src/NHibernate.Test/MultiTenancy/TenantIsolatatedWork.cs +++ b/src/NHibernate.Test/MultiTenancy/TenantIsolatatedWork.cs @@ -1,5 +1,5 @@ using System.Data.Common; -using Microsoft.Data.SqlClient; +using System.Data.SqlClient; using NHibernate.Engine.Transaction; namespace NHibernate.Test.MultiTenancy @@ -15,8 +15,7 @@ public TenantIsolatatedWork(string tenantName) public void DoWork(DbConnection connection, DbTransaction transaction) { - var con = (SqlConnection) connection; - var builder = new SqlConnectionStringBuilder(con.ConnectionString); + var builder = new SqlConnectionStringBuilder(connection.ConnectionString); if (builder.ApplicationName != _tenantName) throw new HibernateException("Invalid tenant connection"); } From 50a0142ec51869eb4e43888eea0231ae83e379f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Delaporte?= <12201973+fredericDelaporte@users.noreply.github.com> Date: Fri, 2 Jul 2021 17:34:55 +0200 Subject: [PATCH 3/4] Document the GetConnection methods --- src/NHibernate/AdoNet/ConnectionManager.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/NHibernate/AdoNet/ConnectionManager.cs b/src/NHibernate/AdoNet/ConnectionManager.cs index 8ea6cd62699..882452b4d98 100644 --- a/src/NHibernate/AdoNet/ConnectionManager.cs +++ b/src/NHibernate/AdoNet/ConnectionManager.cs @@ -224,11 +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) From 889b4a8d11de3f328a68c066b9e2302930c34f36 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Fri, 2 Jul 2021 21:10:15 +0300 Subject: [PATCH 4/4] Async regen --- src/NHibernate/Async/AdoNet/ConnectionManager.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/NHibernate/Async/AdoNet/ConnectionManager.cs b/src/NHibernate/Async/AdoNet/ConnectionManager.cs index 0d93aecab15..4032066ee0a 100644 --- a/src/NHibernate/Async/AdoNet/ConnectionManager.cs +++ b/src/NHibernate/Async/AdoNet/ConnectionManager.cs @@ -25,6 +25,11 @@ 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) @@ -34,6 +39,11 @@ public Task GetNewConnectionAsync(CancellationToken cancellationTo 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)