From 58ff5c5f097f5db9c312e206b149eff20e64daf6 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Mon, 8 Apr 2019 15:08:23 +0300 Subject: [PATCH 01/27] MultiTenancy: Implement tenant per Database strategy --- .../Async/CacheTest/CacheFixture.cs | 2 +- .../Async/DebugSessionFactory.cs | 1 + .../Async/FilterTest/DynamicFilterTest.cs | 2 +- .../DatabaseStrategyNoDbSpecificFixture.cs | 174 +++++++++++++ src/NHibernate.Test/CacheTest/CacheFixture.cs | 2 +- .../CacheTest/QueryKeyFixture.cs | 14 +- src/NHibernate.Test/DebugSessionFactory.cs | 9 +- .../FilterTest/DynamicFilterTest.cs | 2 +- .../DatabaseStrategyNoDbSpecificFixture.cs | 240 ++++++++++++++++++ src/NHibernate.Test/TestCase.cs | 2 +- src/NHibernate/Action/CollectionAction.cs | 2 +- src/NHibernate/AdoNet/ConnectionManager.cs | 26 +- .../Async/Action/CollectionAction.cs | 11 +- .../Async/AdoNet/ConnectionManager.cs | 4 +- .../Async/Connection/ConnectionProvider.cs | 6 + .../Connection/DriverConnectionProvider.cs | 15 +- .../Async/Connection/IConnectionAccess.cs | 22 ++ .../Async/Connection/IConnectionProvider.cs | 22 +- .../Async/Impl/AbstractSessionImpl.cs | 15 ++ .../Async/Impl/SessionFactoryImpl.cs | 1 + .../AbstractMultiTenantConnectionProvider.cs | 36 +++ src/NHibernate/Cache/CacheKey.cs | 25 +- src/NHibernate/Cache/QueryKey.cs | 46 ++++ src/NHibernate/Cfg/Environment.cs | 6 + src/NHibernate/Cfg/Settings.cs | 3 + src/NHibernate/Cfg/SettingsFactory.cs | 6 + .../Connection/ConnectionProvider.cs | 9 +- .../Connection/DriverConnectionProvider.cs | 9 +- .../Connection/IConnectionAccess.cs | 20 ++ .../Connection/IConnectionProvider.cs | 18 +- src/NHibernate/Engine/ISessionImplementor.cs | 15 ++ src/NHibernate/ISessionBuilder.cs | 16 +- src/NHibernate/Impl/AbstractSessionImpl.cs | 54 +++- .../Impl/ISessionCreationOptions.cs | 9 +- src/NHibernate/Impl/SessionFactoryImpl.cs | 16 +- src/NHibernate/Loader/Loader.cs | 2 +- .../AbstractMultiTenantConnectionProvider.cs | 49 ++++ .../IMultiTenantConnectionProvider.cs | 15 ++ .../MultiTenancy/MultiTenancyStrategy.cs | 25 ++ .../MultiTenancy/TenantConfiguration.cs | 19 ++ src/NHibernate/Util/PropertiesHelper.cs | 11 + 41 files changed, 942 insertions(+), 39 deletions(-) create mode 100644 src/NHibernate.Test/Async/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs create mode 100644 src/NHibernate.Test/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs create mode 100644 src/NHibernate/Async/Connection/IConnectionAccess.cs create mode 100644 src/NHibernate/Async/MultiTenancy/AbstractMultiTenantConnectionProvider.cs create mode 100644 src/NHibernate/Connection/IConnectionAccess.cs create mode 100644 src/NHibernate/MultiTenancy/AbstractMultiTenantConnectionProvider.cs create mode 100644 src/NHibernate/MultiTenancy/IMultiTenantConnectionProvider.cs create mode 100644 src/NHibernate/MultiTenancy/MultiTenancyStrategy.cs create mode 100644 src/NHibernate/MultiTenancy/TenantConfiguration.cs diff --git a/src/NHibernate.Test/Async/CacheTest/CacheFixture.cs b/src/NHibernate.Test/Async/CacheTest/CacheFixture.cs index ad8af003134..5d12747ec68 100644 --- a/src/NHibernate.Test/Async/CacheTest/CacheFixture.cs +++ b/src/NHibernate.Test/Async/CacheTest/CacheFixture.cs @@ -29,7 +29,7 @@ public async Task TestSimpleCacheAsync() private CacheKey CreateCacheKey(string text) { - return new CacheKey(text, NHibernateUtil.String, "Foo", null); + return new CacheKey(text, NHibernateUtil.String, "Foo", null, null); } public async Task DoTestCacheAsync(ICacheProvider cacheProvider, CancellationToken cancellationToken = default(CancellationToken)) diff --git a/src/NHibernate.Test/Async/DebugSessionFactory.cs b/src/NHibernate.Test/Async/DebugSessionFactory.cs index 54b9e7ac6ad..5c24cd724c5 100644 --- a/src/NHibernate.Test/Async/DebugSessionFactory.cs +++ b/src/NHibernate.Test/Async/DebugSessionFactory.cs @@ -26,6 +26,7 @@ using NHibernate.Id; using NHibernate.Impl; using NHibernate.Metadata; +using NHibernate.MultiTenancy; using NHibernate.Persister.Collection; using NHibernate.Persister.Entity; using NHibernate.Proxy; diff --git a/src/NHibernate.Test/Async/FilterTest/DynamicFilterTest.cs b/src/NHibernate.Test/Async/FilterTest/DynamicFilterTest.cs index 96d43ef72d0..9dd89c37955 100644 --- a/src/NHibernate.Test/Async/FilterTest/DynamicFilterTest.cs +++ b/src/NHibernate.Test/Async/FilterTest/DynamicFilterTest.cs @@ -45,7 +45,7 @@ public async Task SecondLevelCachedCollectionsFilteringAsync() var persister = Sfi .GetCollectionPersister(typeof(Salesperson).FullName + ".Orders"); var cacheKey = - new CacheKey(testData.steveId, persister.KeyType, persister.Role, Sfi); + new CacheKey(testData.steveId, persister.KeyType, persister.Role, Sfi, null); CollectionCacheEntry cachedData; using (var session = OpenSession()) diff --git a/src/NHibernate.Test/Async/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs b/src/NHibernate.Test/Async/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs new file mode 100644 index 00000000000..ad509afe1b1 --- /dev/null +++ b/src/NHibernate.Test/Async/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs @@ -0,0 +1,174 @@ +//------------------------------------------------------------------------------ +// +// 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; +using System.Data.SqlClient; +using System.Linq; +using NHibernate.Cfg; +using NHibernate.Cfg.MappingSchema; +using NHibernate.Connection; +using NHibernate.Dialect; +using NHibernate.Engine; +using NHibernate.Linq; +using NHibernate.Mapping.ByCode; +using NHibernate.MultiTenancy; +using NUnit.Framework; + +namespace NHibernate.Test.MultiTenancy +{ + using System.Threading.Tasks; + [TestFixture] + public class DatabaseStrategyNoDbSpecificFixtureAsync : TestCaseMappingByCode + { + private Guid _id; + + protected override void Configure(Configuration configuration) + { + configuration.Properties[Cfg.Environment.MultiTenant] = MultiTenancyStrategy.Database.ToString(); + configuration.Properties[Cfg.Environment.GenerateStatistics] = true.ToString(); + base.Configure(configuration); + } + + [Test] + public async Task SecondLevelCacheReusedForSameTenantAsync() + { + using (var sesTen1 = OpenTenantSession("tenant1")) + { + var entity = await (sesTen1.GetAsync(_id)); + } + + Sfi.Statistics.Clear(); + using (var sesTen2 = OpenTenantSession("tenant1")) + { + var entity = await (sesTen2.GetAsync(_id)); + } + + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0)); + Assert.That(Sfi.Statistics.SecondLevelCacheHitCount, Is.EqualTo(1)); + } + + [Test] + public async Task SecondLevelCacheSeparationPerTenantAsync() + { + using (var sesTen1 = OpenTenantSession("tenant1")) + { + var entity = await (sesTen1.GetAsync(_id)); + } + + Sfi.Statistics.Clear(); + using (var sesTen2 = OpenTenantSession("tenant2")) + { + var entity = await (sesTen2.GetAsync(_id)); + } + + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(1)); + Assert.That(Sfi.Statistics.SecondLevelCacheHitCount, Is.EqualTo(0)); + } + + [Test] + public async Task QueryCacheReusedForSameTenantAsync() + { + using (var sesTen1 = OpenTenantSession("tenant1")) + { + var entity = await (sesTen1.Query().WithOptions(x => x.SetCacheable(true)).Where(e => e.Id == _id).SingleOrDefaultAsync()); + } + + Sfi.Statistics.Clear(); + using (var sesTen2 = OpenTenantSession("tenant1")) + { + var entity = await (sesTen2.Query().WithOptions(x => x.SetCacheable(true)).Where(e => e.Id == _id).SingleOrDefaultAsync()); + } + + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0)); + Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(1)); + } + + [Test] + public async Task QueryCacheSeparationPerTenantAsync() + { + using (var sesTen1 = OpenTenantSession("tenant1")) + { + var entity = await (sesTen1.Query().WithOptions(x => x.SetCacheable(true)).Where(e => e.Id == _id).SingleOrDefaultAsync()); + } + + Sfi.Statistics.Clear(); + using (var sesTen2 = OpenTenantSession("tenant2")) + { + var entity = await (sesTen2.Query().WithOptions(x => x.SetCacheable(true)).Where(e => e.Id == _id).SingleOrDefaultAsync()); + } + + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(1)); + Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(0)); + } + + private ISession OpenTenantSession(string tenantId) + { + return Sfi.WithOptions().TenantConfiguration(GetTenantConfig(tenantId)).OpenSession(); + } + + private TenantConfiguration GetTenantConfig(string tenantId) + { + return new TenantConfiguration(new TestTenantConnectionProvider(Sfi, tenantId)); + } + + #region Test Setup + + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + + mapper.Class( + rc => + { + rc.Cache(m => m.Usage(CacheUsage.NonstrictReadWrite)); + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + + protected override ISession OpenSession() + { + return OpenTenantSession("defaultTenant"); + } + + protected override void OnTearDown() + { + using (ISession session = OpenSession()) + using (ITransaction transaction = session.BeginTransaction()) + { + session.Delete("from System.Object"); + + session.Flush(); + transaction.Commit(); + } + } + + protected override void OnSetUp() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + var e1 = new Entity {Name = "Bob"}; + session.Save(e1); + + var e2 = new Entity {Name = "Sally"}; + session.Save(e2); + + session.Flush(); + transaction.Commit(); + _id = e1.Id; + } + } + + #endregion Test Setup + } +} diff --git a/src/NHibernate.Test/CacheTest/CacheFixture.cs b/src/NHibernate.Test/CacheTest/CacheFixture.cs index 2c96ba341dd..5c86f6ced46 100644 --- a/src/NHibernate.Test/CacheTest/CacheFixture.cs +++ b/src/NHibernate.Test/CacheTest/CacheFixture.cs @@ -18,7 +18,7 @@ public void TestSimpleCache() private CacheKey CreateCacheKey(string text) { - return new CacheKey(text, NHibernateUtil.String, "Foo", null); + return new CacheKey(text, NHibernateUtil.String, "Foo", null, null); } public void DoTestCache(ICacheProvider cacheProvider) diff --git a/src/NHibernate.Test/CacheTest/QueryKeyFixture.cs b/src/NHibernate.Test/CacheTest/QueryKeyFixture.cs index dd62799eb28..6ed9fd8a584 100644 --- a/src/NHibernate.Test/CacheTest/QueryKeyFixture.cs +++ b/src/NHibernate.Test/CacheTest/QueryKeyFixture.cs @@ -35,13 +35,13 @@ private void QueryKeyFilterDescLikeToCompare(out QueryKey qk, out QueryKey qk1, f.SetParameter("pLike", "so%"); var fk = new FilterKey(f); ISet fks = new HashSet { fk }; - qk = new QueryKey(Sfi, SqlAll, new QueryParameters(), fks, null); + qk = new QueryKey(Sfi, SqlAll, new QueryParameters(), fks, null, null); var f1 = new FilterImpl(Sfi.GetFilterDefinition(filterName)); f1.SetParameter("pLike", sameValue ? "so%" : "%ing"); var fk1 = new FilterKey(f1); fks = new HashSet { fk1 }; - qk1 = new QueryKey(Sfi, SqlAll, new QueryParameters(), fks, null); + qk1 = new QueryKey(Sfi, SqlAll, new QueryParameters(), fks, null, null); } private void QueryKeyFilterDescValueToCompare(out QueryKey qk, out QueryKey qk1, bool sameValue) @@ -52,13 +52,13 @@ private void QueryKeyFilterDescValueToCompare(out QueryKey qk, out QueryKey qk1, f.SetParameter("pDesc", "something").SetParameter("pValue", 10); var fk = new FilterKey(f); ISet fks = new HashSet { fk }; - qk = new QueryKey(Sfi, SqlAll, new QueryParameters(), fks, null); + qk = new QueryKey(Sfi, SqlAll, new QueryParameters(), fks, null, null); var f1 = new FilterImpl(Sfi.GetFilterDefinition(filterName)); f1.SetParameter("pDesc", "something").SetParameter("pValue", sameValue ? 10 : 11); var fk1 = new FilterKey(f1); fks = new HashSet { fk1 }; - qk1 = new QueryKey(Sfi, SqlAll, new QueryParameters(), fks, null); + qk1 = new QueryKey(Sfi, SqlAll, new QueryParameters(), fks, null, null); } [Test] @@ -122,7 +122,7 @@ public void ToStringWithFilters() f.SetParameter("pLike", "so%"); var fk = new FilterKey(f); ISet fks = new HashSet { fk }; - var qk = new QueryKey(Sfi, SqlAll, new QueryParameters(), fks, null); + var qk = new QueryKey(Sfi, SqlAll, new QueryParameters(), fks, null, null); Assert.That(qk.ToString(), Does.Contain($"filters: ['{fk}']"), "Like"); filterName = "DescriptionEqualAndValueGT"; @@ -130,7 +130,7 @@ public void ToStringWithFilters() f.SetParameter("pDesc", "something").SetParameter("pValue", 10); fk = new FilterKey(f); fks = new HashSet { fk }; - qk = new QueryKey(Sfi, SqlAll, new QueryParameters(), fks, null); + qk = new QueryKey(Sfi, SqlAll, new QueryParameters(), fks, null, null); Assert.That(qk.ToString(), Does.Contain($"filters: ['{fk}']"), "Value"); } @@ -148,7 +148,7 @@ public void ToStringWithMoreFilters() var fvk = new FilterKey(fv); ISet fks = new HashSet { fk, fvk }; - var qk = new QueryKey(Sfi, SqlAll, new QueryParameters(), fks, null); + var qk = new QueryKey(Sfi, SqlAll, new QueryParameters(), fks, null, null); Assert.That(qk.ToString(), Does.Contain($"filters: ['{fk}', '{fvk}']")); } } diff --git a/src/NHibernate.Test/DebugSessionFactory.cs b/src/NHibernate.Test/DebugSessionFactory.cs index eb43b825eee..122184ba5f1 100644 --- a/src/NHibernate.Test/DebugSessionFactory.cs +++ b/src/NHibernate.Test/DebugSessionFactory.cs @@ -16,6 +16,7 @@ using NHibernate.Id; using NHibernate.Impl; using NHibernate.Metadata; +using NHibernate.MultiTenancy; using NHibernate.Persister.Collection; using NHibernate.Persister.Entity; using NHibernate.Proxy; @@ -400,7 +401,7 @@ public static ISessionCreationOptions GetCreationOptions(IStatelessSessionBuilde (ISessionCreationOptions)sessionBuilder; } - internal class SessionBuilder : ISessionBuilder + internal class SessionBuilder : ISessionBuilder, ISessionCreationOptionsWithMultiTenancy { private readonly ISessionBuilder _actualBuilder; private readonly DebugSessionFactory _debugFactory; @@ -465,6 +466,12 @@ ISessionBuilder ISessionBuilder.FlushMode(FlushMode flushMode) } #endregion + + TenantConfiguration ISessionCreationOptionsWithMultiTenancy.TenantConfiguration + { + get => (_actualBuilder as ISessionCreationOptionsWithMultiTenancy)?.TenantConfiguration; + set => _actualBuilder.TenantConfiguration(value); + } } internal class StatelessSessionBuilder : IStatelessSessionBuilder diff --git a/src/NHibernate.Test/FilterTest/DynamicFilterTest.cs b/src/NHibernate.Test/FilterTest/DynamicFilterTest.cs index b345b642ab8..5830894d526 100644 --- a/src/NHibernate.Test/FilterTest/DynamicFilterTest.cs +++ b/src/NHibernate.Test/FilterTest/DynamicFilterTest.cs @@ -33,7 +33,7 @@ public void SecondLevelCachedCollectionsFiltering() var persister = Sfi .GetCollectionPersister(typeof(Salesperson).FullName + ".Orders"); var cacheKey = - new CacheKey(testData.steveId, persister.KeyType, persister.Role, Sfi); + new CacheKey(testData.steveId, persister.KeyType, persister.Role, Sfi, null); CollectionCacheEntry cachedData; using (var session = OpenSession()) diff --git a/src/NHibernate.Test/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs b/src/NHibernate.Test/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs new file mode 100644 index 00000000000..8d66ef3f0e4 --- /dev/null +++ b/src/NHibernate.Test/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs @@ -0,0 +1,240 @@ +using System; +using System.Data.SqlClient; +using System.Linq; +using NHibernate.Cfg; +using NHibernate.Cfg.MappingSchema; +using NHibernate.Connection; +using NHibernate.Dialect; +using NHibernate.Engine; +using NHibernate.Linq; +using NHibernate.Mapping.ByCode; +using NHibernate.MultiTenancy; +using NUnit.Framework; + +namespace NHibernate.Test.MultiTenancy +{ + [TestFixture] + public class DatabaseStrategyNoDbSpecificFixture : TestCaseMappingByCode + { + private Guid _id; + + protected override void Configure(Configuration configuration) + { + configuration.Properties[Cfg.Environment.MultiTenant] = MultiTenancyStrategy.Database.ToString(); + configuration.Properties[Cfg.Environment.GenerateStatistics] = true.ToString(); + base.Configure(configuration); + } + + [Test] + public void ShouldThrowWithNoTenantIdentifier() + { + var sessionBuilder = Sfi.WithOptions().TenantConfiguration(new TenantConfiguration(new MockConnectionProvider(null, null))); + + Assert.That(() => sessionBuilder.OpenSession(), Throws.ArgumentException); + } + + [Test] + public void ShouldThrowWithNoConnectionAccess() + { + var sessionBuilder = Sfi.WithOptions().TenantConfiguration(new TenantConfiguration(new MockConnectionProvider("tenant1", null))); + + Assert.That(() => sessionBuilder.OpenSession(), Throws.ArgumentException); + } + + [Test] + public void DifferentConnectionStringForDifferentTenants() + { + if (!(Sfi.Dialect is MsSql2005Dialect)) + Assert.Ignore("MSSqlServer specific test"); + + using(var session1 = OpenTenantSession("tenant1")) + using (var session2 = OpenTenantSession("tenant2")) + { + Assert.That(session1.Connection.ConnectionString, Is.Not.EqualTo(session2.Connection.ConnectionString)); + var builder1 = new SqlConnectionStringBuilder(session1.Connection.ConnectionString); + var builder2 = new SqlConnectionStringBuilder(session2.Connection.ConnectionString); + Assert.That(builder1.ApplicationName, Is.EqualTo("tenant1")); + Assert.That(builder2.ApplicationName, Is.EqualTo("tenant2")); + } + } + + [Test] + public void SecondLevelCacheReusedForSameTenant() + { + using (var sesTen1 = OpenTenantSession("tenant1")) + { + var entity = sesTen1.Get(_id); + } + + Sfi.Statistics.Clear(); + using (var sesTen2 = OpenTenantSession("tenant1")) + { + var entity = sesTen2.Get(_id); + } + + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0)); + Assert.That(Sfi.Statistics.SecondLevelCacheHitCount, Is.EqualTo(1)); + } + + [Test] + public void SecondLevelCacheSeparationPerTenant() + { + using (var sesTen1 = OpenTenantSession("tenant1")) + { + var entity = sesTen1.Get(_id); + } + + Sfi.Statistics.Clear(); + using (var sesTen2 = OpenTenantSession("tenant2")) + { + var entity = sesTen2.Get(_id); + } + + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(1)); + Assert.That(Sfi.Statistics.SecondLevelCacheHitCount, Is.EqualTo(0)); + } + + [Test] + public void QueryCacheReusedForSameTenant() + { + using (var sesTen1 = OpenTenantSession("tenant1")) + { + var entity = sesTen1.Query().WithOptions(x => x.SetCacheable(true)).Where(e => e.Id == _id).SingleOrDefault(); + } + + Sfi.Statistics.Clear(); + using (var sesTen2 = OpenTenantSession("tenant1")) + { + var entity = sesTen2.Query().WithOptions(x => x.SetCacheable(true)).Where(e => e.Id == _id).SingleOrDefault(); + } + + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0)); + Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(1)); + } + + [Test] + public void QueryCacheSeparationPerTenant() + { + using (var sesTen1 = OpenTenantSession("tenant1")) + { + var entity = sesTen1.Query().WithOptions(x => x.SetCacheable(true)).Where(e => e.Id == _id).SingleOrDefault(); + } + + Sfi.Statistics.Clear(); + using (var sesTen2 = OpenTenantSession("tenant2")) + { + var entity = sesTen2.Query().WithOptions(x => x.SetCacheable(true)).Where(e => e.Id == _id).SingleOrDefault(); + } + + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(1)); + Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(0)); + } + + private ISession OpenTenantSession(string tenantId) + { + return Sfi.WithOptions().TenantConfiguration(GetTenantConfig(tenantId)).OpenSession(); + } + + private TenantConfiguration GetTenantConfig(string tenantId) + { + return new TenantConfiguration(new TestTenantConnectionProvider(Sfi, tenantId)); + } + + #region Test Setup + + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + + mapper.Class( + rc => + { + rc.Cache(m => m.Usage(CacheUsage.NonstrictReadWrite)); + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + + protected override ISession OpenSession() + { + return OpenTenantSession("defaultTenant"); + } + + protected override void OnTearDown() + { + using (ISession session = OpenSession()) + using (ITransaction transaction = session.BeginTransaction()) + { + session.Delete("from System.Object"); + + session.Flush(); + transaction.Commit(); + } + } + + protected override void OnSetUp() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + var e1 = new Entity {Name = "Bob"}; + session.Save(e1); + + var e2 = new Entity {Name = "Sally"}; + session.Save(e2); + + session.Flush(); + transaction.Commit(); + _id = e1.Id; + } + } + + #endregion Test Setup + } + class Entity + { + public virtual Guid Id { get; set; } + public virtual string Name { get; set; } + } + + public class MockConnectionProvider : IMultiTenantConnectionProvider + { + private readonly IConnectionAccess _connectionAccess; + + public MockConnectionProvider(string tenantIdentifier, IConnectionAccess connectionAccess) + { + _connectionAccess = connectionAccess; + TenantIdentifier = tenantIdentifier; + } + + public string TenantIdentifier { get; } + public IConnectionAccess GetConnectionAccess() + { + return _connectionAccess; + } + } + + public class TestTenantConnectionProvider : AbstractMultiTenantConnectionProvider + { + public TestTenantConnectionProvider(ISessionFactoryImplementor sfi, string tenantId) + { + TenantIdentifier = tenantId; + ConnectionProvider = sfi.ConnectionProvider; + TenantConnectionString = sfi.ConnectionProvider.GetConnectionString(); + if (sfi.Dialect is MsSql2005Dialect) + { + var stringBuilder = new SqlConnectionStringBuilder(ConnectionProvider.GetConnectionString()); + stringBuilder.ApplicationName = tenantId; + TenantConnectionString = stringBuilder.ToString(); + } + } + + protected override string TenantConnectionString { get; } + + public override string TenantIdentifier { get; } + + protected override IConnectionProvider ConnectionProvider { get; } + } +} diff --git a/src/NHibernate.Test/TestCase.cs b/src/NHibernate.Test/TestCase.cs index f7505d3971f..bee3ebeb9fc 100644 --- a/src/NHibernate.Test/TestCase.cs +++ b/src/NHibernate.Test/TestCase.cs @@ -237,7 +237,7 @@ protected virtual bool CheckDatabaseWasCleaned() } bool empty; - using (ISession s = Sfi.OpenSession()) + using (ISession s = OpenSession()) { IList objects = s.CreateQuery("from System.Object o").List(); empty = objects.Count == 0; diff --git a/src/NHibernate/Action/CollectionAction.cs b/src/NHibernate/Action/CollectionAction.cs index ca5be713bf6..e676e7d4f18 100644 --- a/src/NHibernate/Action/CollectionAction.cs +++ b/src/NHibernate/Action/CollectionAction.cs @@ -124,7 +124,7 @@ public virtual void BeforeExecutions() public virtual void ExecuteAfterTransactionCompletion(bool success) { - var ck = new CacheKey(key, persister.KeyType, persister.Role, Session.Factory); + var ck = session.GenerateCacheKey(key, persister.KeyType, persister.Role); persister.Cache.Release(ck, softLock); } diff --git a/src/NHibernate/AdoNet/ConnectionManager.cs b/src/NHibernate/AdoNet/ConnectionManager.cs index ee7f4898651..b9457ec8209 100644 --- a/src/NHibernate/AdoNet/ConnectionManager.cs +++ b/src/NHibernate/AdoNet/ConnectionManager.cs @@ -4,7 +4,7 @@ using System.Data.Common; using System.Runtime.Serialization; using System.Security; - +using NHibernate.Connection; using NHibernate.Engine; namespace NHibernate.AdoNet @@ -19,6 +19,7 @@ namespace NHibernate.AdoNet [Serializable] public partial class ConnectionManager : ISerializable, IDeserializationCallback { + private readonly IConnectionAccess _connectionAccess; private static readonly INHibernateLogger _log = NHibernateLogger.For(typeof(ConnectionManager)); [NonSerialized] @@ -73,6 +74,23 @@ public partial class ConnectionManager : ISerializable, IDeserializationCallback /// public bool ProcessingFromSystemTransaction => _processingFromSystemTransaction; + public ConnectionManager( + ISessionImplementor session, + DbConnection suppliedConnection, + ConnectionReleaseMode connectionReleaseMode, + IInterceptor interceptor, + IConnectionAccess connectionAccess, + bool shouldAutoJoinTransaction) +#pragma warning disable 618 + : this(session, suppliedConnection, connectionReleaseMode, interceptor, shouldAutoJoinTransaction) +#pragma warning restore 618 + + { + _connectionAccess = connectionAccess; + } + + //Since 5.3 + [Obsolete("Use overload with connectionAccess parameter")] public ConnectionManager( ISessionImplementor session, DbConnection suppliedConnection, @@ -148,7 +166,7 @@ public DbConnection Close() if (_backupConnection != null) { _log.Warn("Backup connection was still defined at time of closing."); - Factory.ConnectionProvider.CloseConnection(_backupConnection); + _connectionAccess.CloseConnection(_backupConnection); _backupConnection = null; } @@ -205,7 +223,7 @@ public DbConnection Disconnect() private void CloseConnection() { - Factory.ConnectionProvider.CloseConnection(_connection); + _connectionAccess.CloseConnection(_connection); _connection = null; } @@ -239,7 +257,7 @@ public DbConnection GetConnection() { if (_ownConnection) { - _connection = Factory.ConnectionProvider.GetConnection(); + _connection = _connectionAccess.GetConnection(); // 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/Action/CollectionAction.cs b/src/NHibernate/Async/Action/CollectionAction.cs index 16a2a27460e..237d2c34afe 100644 --- a/src/NHibernate/Async/Action/CollectionAction.cs +++ b/src/NHibernate/Async/Action/CollectionAction.cs @@ -74,8 +74,15 @@ public virtual Task ExecuteAfterTransactionCompletionAsync(bool success, Cancell { return Task.FromCanceled(cancellationToken); } - var ck = new CacheKey(key, persister.KeyType, persister.Role, Session.Factory); - return persister.Cache.ReleaseAsync(ck, softLock, cancellationToken); + try + { + var ck = session.GenerateCacheKey(key, persister.KeyType, persister.Role); + return persister.Cache.ReleaseAsync(ck, softLock, cancellationToken); + } + catch (Exception ex) + { + return Task.FromException(ex); + } } #endregion diff --git a/src/NHibernate/Async/AdoNet/ConnectionManager.cs b/src/NHibernate/Async/AdoNet/ConnectionManager.cs index 5ea057310b2..a716845c6e9 100644 --- a/src/NHibernate/Async/AdoNet/ConnectionManager.cs +++ b/src/NHibernate/Async/AdoNet/ConnectionManager.cs @@ -14,7 +14,7 @@ using System.Data.Common; using System.Runtime.Serialization; using System.Security; - +using NHibernate.Connection; using NHibernate.Engine; namespace NHibernate.AdoNet @@ -61,7 +61,7 @@ async Task InternalGetConnectionAsync() { if (_ownConnection) { - _connection = await (Factory.ConnectionProvider.GetConnectionAsync(cancellationToken)).ConfigureAwait(false); + _connection = await (_connectionAccess.GetConnectionAsync(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/Connection/ConnectionProvider.cs b/src/NHibernate/Async/Connection/ConnectionProvider.cs index 3d04b34668b..fbdaac63ad4 100644 --- a/src/NHibernate/Async/Connection/ConnectionProvider.cs +++ b/src/NHibernate/Async/Connection/ConnectionProvider.cs @@ -30,5 +30,11 @@ public abstract partial class ConnectionProvider : IConnectionProvider /// A cancellation token that can be used to cancel the work /// An open . public abstract Task GetConnectionAsync(CancellationToken cancellationToken); + + //TODO 6.0: Make abstract + public virtual Task GetConnectionAsync(string connectionString, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } } } diff --git a/src/NHibernate/Async/Connection/DriverConnectionProvider.cs b/src/NHibernate/Async/Connection/DriverConnectionProvider.cs index 4c3358a5943..72a96753f56 100644 --- a/src/NHibernate/Async/Connection/DriverConnectionProvider.cs +++ b/src/NHibernate/Async/Connection/DriverConnectionProvider.cs @@ -29,14 +29,23 @@ public partial class DriverConnectionProvider : ConnectionProvider /// /// If there is any problem creating or opening the . /// - public override async Task GetConnectionAsync(CancellationToken cancellationToken) + public override Task GetConnectionAsync(CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + return GetConnectionAsync(ConnectionString, cancellationToken); + } + + public override async Task GetConnectionAsync(string connectionString, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); log.Debug("Obtaining DbConnection from Driver"); var conn = Driver.CreateConnection(); try { - conn.ConnectionString = ConnectionString; + conn.ConnectionString = connectionString; await (conn.OpenAsync(cancellationToken)).ConfigureAwait(false); } catch (OperationCanceledException) { throw; } @@ -49,4 +58,4 @@ public override async Task GetConnectionAsync(CancellationToken ca return conn; } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Async/Connection/IConnectionAccess.cs b/src/NHibernate/Async/Connection/IConnectionAccess.cs new file mode 100644 index 00000000000..6cfb3c5acbc --- /dev/null +++ b/src/NHibernate/Async/Connection/IConnectionAccess.cs @@ -0,0 +1,22 @@ +//------------------------------------------------------------------------------ +// +// 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; + +namespace NHibernate.Connection +{ + using System.Threading.Tasks; + using System.Threading; + public partial interface IConnectionAccess + { + //ObtainConnection in hibernate + Task GetConnectionAsync(CancellationToken cancellationToken); + } +} diff --git a/src/NHibernate/Async/Connection/IConnectionProvider.cs b/src/NHibernate/Async/Connection/IConnectionProvider.cs index 61587ec2e6f..3a74648f9fd 100644 --- a/src/NHibernate/Async/Connection/IConnectionProvider.cs +++ b/src/NHibernate/Async/Connection/IConnectionProvider.cs @@ -12,11 +12,31 @@ using System.Collections.Generic; using System.Data.Common; using NHibernate.Driver; +using NHibernate.Util; namespace NHibernate.Connection { using System.Threading.Tasks; using System.Threading; + public static partial class ConnectionProviderExtensions + { + internal static Task GetConnectionAsync(this IConnectionProvider connectionProvider, string connectionString, CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + try + { + return ReflectHelper.CastOrThrow(connectionProvider, "open connection by connectionString").GetConnectionAsync(connectionString, cancellationToken); + } + catch (Exception ex) + { + return Task.FromException(ex); + } + } + } + public partial interface IConnectionProvider : IDisposable { @@ -27,4 +47,4 @@ public partial interface IConnectionProvider : IDisposable /// An open . Task GetConnectionAsync(CancellationToken cancellationToken); } -} \ No newline at end of file +} diff --git a/src/NHibernate/Async/Impl/AbstractSessionImpl.cs b/src/NHibernate/Async/Impl/AbstractSessionImpl.cs index be4e02a2734..9acba2eeffb 100644 --- a/src/NHibernate/Async/Impl/AbstractSessionImpl.cs +++ b/src/NHibernate/Async/Impl/AbstractSessionImpl.cs @@ -17,6 +17,7 @@ using NHibernate.AdoNet; using NHibernate.Cache; using NHibernate.Collection; +using NHibernate.Connection; using NHibernate.Engine; using NHibernate.Engine.Query; using NHibernate.Engine.Query.Sql; @@ -27,6 +28,7 @@ using NHibernate.Loader.Custom; using NHibernate.Loader.Custom.Sql; using NHibernate.Multi; +using NHibernate.MultiTenancy; using NHibernate.Persister.Entity; using NHibernate.Transaction; using NHibernate.Type; @@ -218,4 +220,17 @@ protected async Task AfterOperationAsync(bool success, CancellationToken cancell public abstract Task ExecuteUpdateAsync(IQueryExpression queryExpression, QueryParameters queryParameters, CancellationToken cancellationToken); } + + partial class NonContextualConnectionAccess : IConnectionAccess + { + + public Task GetConnectionAsync(CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + return _connectionProvider.GetConnectionAsync(cancellationToken); + } + } } diff --git a/src/NHibernate/Async/Impl/SessionFactoryImpl.cs b/src/NHibernate/Async/Impl/SessionFactoryImpl.cs index 8fb68cb28cb..dbcbdab24d0 100644 --- a/src/NHibernate/Async/Impl/SessionFactoryImpl.cs +++ b/src/NHibernate/Async/Impl/SessionFactoryImpl.cs @@ -31,6 +31,7 @@ using NHibernate.Id; using NHibernate.Mapping; using NHibernate.Metadata; +using NHibernate.MultiTenancy; using NHibernate.Persister; using NHibernate.Persister.Collection; using NHibernate.Persister.Entity; diff --git a/src/NHibernate/Async/MultiTenancy/AbstractMultiTenantConnectionProvider.cs b/src/NHibernate/Async/MultiTenancy/AbstractMultiTenantConnectionProvider.cs new file mode 100644 index 00000000000..e46dae344c6 --- /dev/null +++ b/src/NHibernate/Async/MultiTenancy/AbstractMultiTenantConnectionProvider.cs @@ -0,0 +1,36 @@ +//------------------------------------------------------------------------------ +// +// 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; +using System.Data.Common; +using NHibernate.Connection; +using NHibernate.Util; + +namespace NHibernate.MultiTenancy +{ + using System.Threading.Tasks; + using System.Threading; + public abstract partial class AbstractMultiTenantConnectionProvider : IMultiTenantConnectionProvider + { + + partial class ContextualConnectionAccess : IConnectionAccess + { + + public Task GetConnectionAsync(CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + return _connectionProvider.GetConnectionAsync(ConnectionString, cancellationToken); + } + } + } +} diff --git a/src/NHibernate/Cache/CacheKey.cs b/src/NHibernate/Cache/CacheKey.cs index 3b0a46d7266..52aab1a35dc 100644 --- a/src/NHibernate/Cache/CacheKey.cs +++ b/src/NHibernate/Cache/CacheKey.cs @@ -20,6 +20,7 @@ public class CacheKey : IDeserializationCallback [NonSerialized] private int? _hashCode; private readonly ISessionFactoryImplementor _factory; + private readonly string _tenantIdentifier; /// /// Construct a new key for a collection or entity instance. @@ -30,6 +31,19 @@ public class CacheKey : IDeserializationCallback /// The Hibernate type mapping /// The entity or collection-role name. /// The session factory for which we are caching + /// + public CacheKey(object id, IType type, string entityOrRoleName, ISessionFactoryImplementor factory, string tenantIdentifier) + { + key = id; + this.type = type; + this.entityOrRoleName = entityOrRoleName; + _factory = factory; + _tenantIdentifier = tenantIdentifier; + + _hashCode = GenerateHashCode(); + } + + [Obsolete("Use constructor with tenantIdentifier")] public CacheKey(object id, IType type, string entityOrRoleName, ISessionFactoryImplementor factory) { key = id; @@ -51,7 +65,7 @@ public override bool Equals(object obj) { CacheKey that = obj as CacheKey; if (that == null) return false; - return entityOrRoleName.Equals(that.entityOrRoleName) && type.IsEqual(key, that.key); + return entityOrRoleName.Equals(that.entityOrRoleName) && type.IsEqual(key, that.key) && _tenantIdentifier == that._tenantIdentifier; } public override int GetHashCode() @@ -71,7 +85,14 @@ public void OnDeserialization(object sender) private int GenerateHashCode() { - return type.GetHashCode(key, _factory); + var hashCode = type.GetHashCode(key, _factory); + + if (_tenantIdentifier != null) + { + hashCode = 37 * hashCode + _tenantIdentifier.GetHashCode(); + } + + return hashCode; } public object Key diff --git a/src/NHibernate/Cache/QueryKey.cs b/src/NHibernate/Cache/QueryKey.cs index c5f9eb17d1b..6ef92227c41 100644 --- a/src/NHibernate/Cache/QueryKey.cs +++ b/src/NHibernate/Cache/QueryKey.cs @@ -21,6 +21,7 @@ public class QueryKey : IDeserializationCallback, IEquatable private readonly object[] _values; private readonly int _firstRow = RowSelection.NoValue; private readonly int _maxRows = RowSelection.NoValue; + private readonly string _tenantIdentifier; // Sets and dictionaries are populated last during deserialization, causing them to be potentially empty // during the deserialization callback. This causes them to be unreliable when used in hashcode or equals @@ -47,6 +48,37 @@ public class QueryKey : IDeserializationCallback, IEquatable /// The query parameters. /// The filters. /// The result transformer; should be null if data is not transformed before being cached. + /// Tenant identifier or null + public QueryKey(ISessionFactoryImplementor factory, SqlString queryString, QueryParameters queryParameters, + ISet filters, CacheableResultTransformer customTransformer, string tenantIdentifier) + { + _factory = factory; + _sqlQueryString = queryString; + _types = queryParameters.PositionalParameterTypes; + _values = queryParameters.PositionalParameterValues; + + RowSelection selection = queryParameters.RowSelection; + if (selection != null) + { + _firstRow = selection.FirstRow; + _maxRows = selection.MaxRows; + } + else + { + _firstRow = RowSelection.NoValue; + _maxRows = RowSelection.NoValue; + } + + _namedParameters = queryParameters.NamedParameters?.ToArray(); + _filters = filters?.ToArray(); + _customTransformer = customTransformer; + _tenantIdentifier = tenantIdentifier; + + _hashCode = ComputeHashCode(); + } + + //Since 5.3 + [Obsolete("Please use overload with tenantIdentifier")] public QueryKey(ISessionFactoryImplementor factory, SqlString queryString, QueryParameters queryParameters, ISet filters, CacheableResultTransformer customTransformer) { @@ -161,6 +193,11 @@ public bool Equals(QueryKey other) { return false; } + + if (_tenantIdentifier != that._tenantIdentifier) + { + return false; + } return true; } @@ -223,6 +260,10 @@ public int ComputeHashCode() result = 37 * result + (_customTransformer == null ? 0 : _customTransformer.GetHashCode()); result = 37 * result + _sqlQueryString.GetHashCode(); + if (_tenantIdentifier != null) + { + result = 37 * result + _tenantIdentifier.GetHashCode(); + } return result; } } @@ -284,6 +325,11 @@ public override string ToString() buf.Append("; "); } + if (_tenantIdentifier != null) + { + buf.Append("; tenantIdentifier=").Append(_tenantIdentifier).Append("; "); + } + return buf.ToString(); } } diff --git a/src/NHibernate/Cfg/Environment.cs b/src/NHibernate/Cfg/Environment.cs index 7c19f5c6421..74f47eeb2a1 100644 --- a/src/NHibernate/Cfg/Environment.cs +++ b/src/NHibernate/Cfg/Environment.cs @@ -7,6 +7,7 @@ using NHibernate.Engine; using NHibernate.Linq; using NHibernate.Linq.Visitors; +using NHibernate.MultiTenancy; using NHibernate.Util; namespace NHibernate.Cfg @@ -385,6 +386,11 @@ public static string Version private static readonly Dictionary GlobalProperties = new Dictionary(); + /// + /// Strategy for multi-tenancy. + /// + public const string MultiTenant = "multiTenancy"; + private static IBytecodeProvider BytecodeProviderInstance; private static bool EnableReflectionOptimizer; diff --git a/src/NHibernate/Cfg/Settings.cs b/src/NHibernate/Cfg/Settings.cs index 080a773b47c..20717d128fa 100644 --- a/src/NHibernate/Cfg/Settings.cs +++ b/src/NHibernate/Cfg/Settings.cs @@ -10,6 +10,7 @@ using NHibernate.Hql; using NHibernate.Linq.Functions; using NHibernate.Linq.Visitors; +using NHibernate.MultiTenancy; using NHibernate.Transaction; namespace NHibernate.Cfg @@ -207,5 +208,7 @@ internal string GetFullCacheRegionName(string name) return prefix + '.' + name; return name; } + + public MultiTenancyStrategy MultiTenancyStrategy { get; internal set; } } } diff --git a/src/NHibernate/Cfg/SettingsFactory.cs b/src/NHibernate/Cfg/SettingsFactory.cs index 4c1aec2ff04..074fc141aa7 100644 --- a/src/NHibernate/Cfg/SettingsFactory.cs +++ b/src/NHibernate/Cfg/SettingsFactory.cs @@ -12,6 +12,7 @@ using NHibernate.Linq; using NHibernate.Linq.Functions; using NHibernate.Linq.Visitors; +using NHibernate.MultiTenancy; using NHibernate.Transaction; using NHibernate.Util; @@ -325,6 +326,11 @@ public Settings BuildSettings(IDictionary properties) log.Debug("Track session id: " + EnabledDisabled(trackSessionId)); settings.TrackSessionId = trackSessionId; + var multiTenancyStrategy = PropertiesHelper.GetEnum(Environment.MultiTenant, properties, MultiTenancyStrategy.None); + if(multiTenancyStrategy != MultiTenancyStrategy.None) + log.Debug("multi-tenancy strategy : " + multiTenancyStrategy); + settings.MultiTenancyStrategy = multiTenancyStrategy; + return settings; } diff --git a/src/NHibernate/Connection/ConnectionProvider.cs b/src/NHibernate/Connection/ConnectionProvider.cs index e30c2fdf21d..ef22ab229f0 100644 --- a/src/NHibernate/Connection/ConnectionProvider.cs +++ b/src/NHibernate/Connection/ConnectionProvider.cs @@ -118,7 +118,8 @@ protected virtual void ConfigureDriver(IDictionary settings) /// The for the /// to connect to the database. /// - protected virtual string ConnectionString + //TODO 6.0: Make public + protected internal virtual string ConnectionString { get { return connString; } } @@ -203,5 +204,11 @@ protected virtual void Dispose(bool isDisposing) } #endregion + + //TODO 6.0: Make abstract + public virtual DbConnection GetConnection(string connectionString) + { + throw new NotImplementedException(); + } } } diff --git a/src/NHibernate/Connection/DriverConnectionProvider.cs b/src/NHibernate/Connection/DriverConnectionProvider.cs index e3c43f4c0d0..a11c0f41d09 100644 --- a/src/NHibernate/Connection/DriverConnectionProvider.cs +++ b/src/NHibernate/Connection/DriverConnectionProvider.cs @@ -31,12 +31,17 @@ public override void CloseConnection(DbConnection conn) /// If there is any problem creating or opening the . /// public override DbConnection GetConnection() + { + return GetConnection(ConnectionString); + } + + public override DbConnection GetConnection(string connectionString) { log.Debug("Obtaining DbConnection from Driver"); var conn = Driver.CreateConnection(); try { - conn.ConnectionString = ConnectionString; + conn.ConnectionString = connectionString; conn.Open(); } catch (Exception) @@ -48,4 +53,4 @@ public override DbConnection GetConnection() return conn; } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Connection/IConnectionAccess.cs b/src/NHibernate/Connection/IConnectionAccess.cs new file mode 100644 index 00000000000..7b92c45327b --- /dev/null +++ b/src/NHibernate/Connection/IConnectionAccess.cs @@ -0,0 +1,20 @@ +using System.Data.Common; + +namespace NHibernate.Connection +{ + //JdbcConnectionAccess.java in hibernate + /// + /// Provides centralized access to connections. Centralized to hide the complexity of accounting for contextual + /// (multi-tenant) versus non-contextual access. + /// + public partial interface IConnectionAccess + { + //ObtainConnection in hibernate + DbConnection GetConnection(); + + //Note: ReleaseConnection in hibernate + void CloseConnection(DbConnection conn); + + string ConnectionString { get; } + } +} diff --git a/src/NHibernate/Connection/IConnectionProvider.cs b/src/NHibernate/Connection/IConnectionProvider.cs index 2657c4090a7..e1238a72d79 100644 --- a/src/NHibernate/Connection/IConnectionProvider.cs +++ b/src/NHibernate/Connection/IConnectionProvider.cs @@ -2,9 +2,25 @@ using System.Collections.Generic; using System.Data.Common; using NHibernate.Driver; +using NHibernate.Util; namespace NHibernate.Connection { + //TODO: Merge into IConnectionProvider + public static partial class ConnectionProviderExtensions + { + internal static DbConnection GetConnection(this IConnectionProvider connectionProvider, string connectionString) + { + return ReflectHelper.CastOrThrow(connectionProvider, "open connection by connectionString").GetConnection(connectionString); + } + + //TODO: Expose as ConnectionString property + public static string GetConnectionString(this IConnectionProvider connectionProvider) + { + return ReflectHelper.CastOrThrow(connectionProvider, "retrieve connectionString").ConnectionString; + } + } + /// /// A strategy for obtaining ADO.NET . /// @@ -42,4 +58,4 @@ public partial interface IConnectionProvider : IDisposable /// An open . DbConnection GetConnection(); } -} \ No newline at end of file +} diff --git a/src/NHibernate/Engine/ISessionImplementor.cs b/src/NHibernate/Engine/ISessionImplementor.cs index 84b30da88ae..3bbaa1f147c 100644 --- a/src/NHibernate/Engine/ISessionImplementor.cs +++ b/src/NHibernate/Engine/ISessionImplementor.cs @@ -21,6 +21,21 @@ namespace NHibernate.Engine // 6.0 TODO: Convert to interface methods, excepted SwitchCacheMode internal static partial class SessionImplementorExtensions { + //6.0 TODO: Expose as TenantIdentifier property + /// + /// Obtain the tenant identifier associated with this session. + /// + /// The tenant identifier associated with this session or null + internal static string GetTenantIdentifier(this ISessionImplementor session) + { + if (session is AbstractSessionImpl sessionImpl) + { + return sessionImpl.TenantIdentifier; + } + + return null; + } + /// /// Instantiate the entity class, initializing with the given identifier /// diff --git a/src/NHibernate/ISessionBuilder.cs b/src/NHibernate/ISessionBuilder.cs index 8fa6112914d..08135d1cc87 100644 --- a/src/NHibernate/ISessionBuilder.cs +++ b/src/NHibernate/ISessionBuilder.cs @@ -1,14 +1,28 @@ using System.Data.Common; using NHibernate.Connection; +using NHibernate.Impl; +using NHibernate.MultiTenancy; +using NHibernate.Util; namespace NHibernate { + //TODO 6.0: Merge into ISessionBuilder + public static class SessionBuilderExtensions + { + public static T TenantConfiguration(this T builder, TenantConfiguration tenantConfig) where T: ISessionBuilder + { + ReflectHelper.CastOrThrow(builder, "multi tenancy").TenantConfiguration = tenantConfig; + return builder; + } + } + // NH specific: Java does not require this, it looks as still having a better covariance support. /// /// Represents a consolidation of all session creation options into a builder style delegate. /// public interface ISessionBuilder : ISessionBuilder { } + //TODO 6.0: Make T covariant ISessionBuilder -> ISessionBuilder /// /// Represents a consolidation of all session creation options into a builder style delegate. /// @@ -82,4 +96,4 @@ public interface ISessionBuilder where T : ISessionBuilder /// , for method chaining. T FlushMode(FlushMode flushMode); } -} \ No newline at end of file +} diff --git a/src/NHibernate/Impl/AbstractSessionImpl.cs b/src/NHibernate/Impl/AbstractSessionImpl.cs index 583b183b75e..a3452e41018 100644 --- a/src/NHibernate/Impl/AbstractSessionImpl.cs +++ b/src/NHibernate/Impl/AbstractSessionImpl.cs @@ -7,6 +7,7 @@ using NHibernate.AdoNet; using NHibernate.Cache; using NHibernate.Collection; +using NHibernate.Connection; using NHibernate.Engine; using NHibernate.Engine.Query; using NHibernate.Engine.Query.Sql; @@ -17,6 +18,7 @@ using NHibernate.Loader.Custom; using NHibernate.Loader.Custom.Sql; using NHibernate.Multi; +using NHibernate.MultiTenancy; using NHibernate.Persister.Entity; using NHibernate.Transaction; using NHibernate.Type; @@ -35,6 +37,8 @@ public abstract partial class AbstractSessionImpl : ISessionImplementor [NonSerialized] private IQueryBatch _futureMultiBatch; + private readonly TenantConfiguration _tenantConfiguration; + private bool closed; /// Get the current NHibernate transaction. @@ -57,6 +61,22 @@ public ITransactionContext TransactionContext internal AbstractSessionImpl() { } + private void ValidateTenantConfiguration(ISessionFactoryImplementor factory, TenantConfiguration tenantConfiguration) + { + if (factory.Settings.MultiTenancyStrategy == MultiTenancyStrategy.None) + return; + + if (string.IsNullOrEmpty(tenantConfiguration?.TenantIdentifier)) + { + throw new ArgumentException("Tenant configuration with TenantIdentifier defined is required for multi-tenancy.", nameof(tenantConfiguration)); + } + + if (factory.Settings.MultiTenancyStrategy == MultiTenancyStrategy.Database && tenantConfiguration.ConnectionAccess == null) + { + throw new ArgumentException($"Tenant configuration with ConnectionAccess defined is required for {factory.Settings.MultiTenancyStrategy} multi-tenancy strategy."); + } + } + protected internal AbstractSessionImpl(ISessionFactoryImplementor factory, ISessionCreationOptions options) { SessionId = factory.Settings.TrackSessionId ? Guid.NewGuid() : Guid.Empty; @@ -67,6 +87,12 @@ protected internal AbstractSessionImpl(ISessionFactoryImplementor factory, ISess _flushMode = options.InitialSessionFlushMode; Interceptor = options.SessionInterceptor ?? EmptyInterceptor.Instance; + if (options is ISessionCreationOptionsWithMultiTenancy multiTenancy) + { + _tenantConfiguration = multiTenancy.TenantConfiguration; + ValidateTenantConfiguration(factory, _tenantConfiguration); + } + if (options is ISharedSessionCreationOptions sharedOptions && sharedOptions.IsTransactionCoordinatorShared) { // NH specific implementation: need to port Hibernate transaction management. @@ -84,6 +110,7 @@ protected internal AbstractSessionImpl(ISessionFactoryImplementor factory, ISess options.UserSuppliedConnection, options.SessionConnectionReleaseMode, Interceptor, + _tenantConfiguration?.ConnectionAccess ?? new NonContextualConnectionAccess(_factory.ConnectionProvider), options.ShouldAutoJoinTransaction); } } @@ -109,7 +136,7 @@ public EntityKey GenerateEntityKey(object id, IEntityPersister persister) public CacheKey GenerateCacheKey(object id, IType type, string entityOrRoleName) { - return new CacheKey(id, type, entityOrRoleName, Factory); + return new CacheKey(id, type, entityOrRoleName, Factory, TenantIdentifier); } public ISessionFactoryImplementor Factory @@ -465,6 +492,8 @@ protected bool IsAlreadyDisposed /// public virtual bool TransactionInProgress => ConnectionManager.IsInActiveTransaction; + public string TenantIdentifier => _tenantConfiguration?.TenantIdentifier; + #endregion protected internal void SetClosed() @@ -669,4 +698,27 @@ public virtual IQueryBatch CreateQueryBatch() return new QueryBatch(this, false); } } +[Serializable] + + partial class NonContextualConnectionAccess : IConnectionAccess + { + private readonly IConnectionProvider _connectionProvider; + + public NonContextualConnectionAccess(IConnectionProvider connectionProvider) + { + _connectionProvider = connectionProvider; + } + + public DbConnection GetConnection() + { + return _connectionProvider.GetConnection(); + } + + public void CloseConnection(DbConnection connection) + { + _connectionProvider.CloseConnection(connection); + } + + public string ConnectionString => _connectionProvider.GetConnectionString(); + } } diff --git a/src/NHibernate/Impl/ISessionCreationOptions.cs b/src/NHibernate/Impl/ISessionCreationOptions.cs index 0e11afa2352..3dbd0632354 100644 --- a/src/NHibernate/Impl/ISessionCreationOptions.cs +++ b/src/NHibernate/Impl/ISessionCreationOptions.cs @@ -1,7 +1,14 @@ using System.Data.Common; +using NHibernate.MultiTenancy; namespace NHibernate.Impl { + public interface ISessionCreationOptionsWithMultiTenancy + { + //TODO 6.0: Merge to ISessionCreationOptions without setter + TenantConfiguration TenantConfiguration { get; set; } + } + /// /// Options for session creation. /// @@ -23,4 +30,4 @@ public interface ISessionCreationOptions // Todo: port PhysicalConnectionHandlingMode ConnectionReleaseMode SessionConnectionReleaseMode { get; } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Impl/SessionFactoryImpl.cs b/src/NHibernate/Impl/SessionFactoryImpl.cs index f381b7162d0..bb109719107 100644 --- a/src/NHibernate/Impl/SessionFactoryImpl.cs +++ b/src/NHibernate/Impl/SessionFactoryImpl.cs @@ -21,6 +21,7 @@ using NHibernate.Id; using NHibernate.Mapping; using NHibernate.Metadata; +using NHibernate.MultiTenancy; using NHibernate.Persister; using NHibernate.Persister.Collection; using NHibernate.Persister.Entity; @@ -1021,7 +1022,7 @@ public void EvictCollection(string roleName, object id) } } - private CacheKey GenerateCacheKeyForEvict(object id, IType type, string entityOrRoleName) + private CacheKey GenerateCacheKeyForEvict(object id, IType type, string entityOrRoleName, string tenantIdentifier = null) { // if there is a session context, use that to generate the key. if (CurrentSessionContext != null) @@ -1032,7 +1033,12 @@ private CacheKey GenerateCacheKeyForEvict(object id, IType type, string entityOr .GenerateCacheKey(id, type, entityOrRoleName); } - return new CacheKey(id, type, entityOrRoleName, this); + if (settings.MultiTenancyStrategy != MultiTenancyStrategy.None && tenantIdentifier == null) + { + throw new NotImplementedException("Eviction is not implemented for multi-tenancy. Please initialize CurrentSessionContext."); + } + + return new CacheKey(id, type, entityOrRoleName, this, null); } public void EvictCollection(string roleName) @@ -1423,7 +1429,7 @@ public SessionBuilderImpl(SessionFactoryImpl sessionFactory) : base(sessionFacto } } - internal class SessionBuilderImpl : ISessionBuilder, ISessionCreationOptions where T : ISessionBuilder + internal class SessionBuilderImpl : ISessionBuilder, ISessionCreationOptions, ISessionCreationOptionsWithMultiTenancy where T : ISessionBuilder { // NH specific: implementing return type covariance with interface is a mess in .Net. private T _this; @@ -1532,6 +1538,10 @@ public virtual T FlushMode(FlushMode flushMode) _flushMode = flushMode; return _this; } + + public TenantConfiguration TenantConfiguration { get; + //TODO 6.0: Make protected + set; } } // NH specific: implementing return type covariance with interface is a mess in .Net. diff --git a/src/NHibernate/Loader/Loader.cs b/src/NHibernate/Loader/Loader.cs index 49fd0974e38..8069cc9a544 100644 --- a/src/NHibernate/Loader/Loader.cs +++ b/src/NHibernate/Loader/Loader.cs @@ -1881,7 +1881,7 @@ internal QueryKey GenerateQueryKey(ISessionImplementor session, QueryParameters { ISet filterKeys = FilterKey.CreateFilterKeys(session.EnabledFilters); return new QueryKey(Factory, SqlString, queryParameters, filterKeys, - CreateCacheableResultTransformer(queryParameters)); + CreateCacheableResultTransformer(queryParameters), session.GetTenantIdentifier()); } private CacheableResultTransformer CreateCacheableResultTransformer(QueryParameters queryParameters) diff --git a/src/NHibernate/MultiTenancy/AbstractMultiTenantConnectionProvider.cs b/src/NHibernate/MultiTenancy/AbstractMultiTenantConnectionProvider.cs new file mode 100644 index 00000000000..3ecdfd74c6a --- /dev/null +++ b/src/NHibernate/MultiTenancy/AbstractMultiTenantConnectionProvider.cs @@ -0,0 +1,49 @@ +using System; +using System.Data.Common; +using NHibernate.Connection; +using NHibernate.Util; + +namespace NHibernate.MultiTenancy +{ + /// + /// Base implementation for multi-tenancy strategy + /// + public abstract partial class AbstractMultiTenantConnectionProvider : IMultiTenantConnectionProvider + { + protected abstract string TenantConnectionString { get; } + public abstract string TenantIdentifier { get; } + + public IConnectionAccess GetConnectionAccess() + { + //TODO 6.0: Remove check + ReflectHelper.CastOrThrow(ConnectionProvider, $"multi-tenancy. For custom connection provider please implement IMultiTenantConnectionProvider directly for '{GetType().Name}' and do not use {nameof(AbstractMultiTenantConnectionProvider)} as base class."); + return new ContextualConnectionAccess(TenantConnectionString, ConnectionProvider); + } + + protected abstract IConnectionProvider ConnectionProvider { get; } +[Serializable] + + partial class ContextualConnectionAccess : IConnectionAccess + { + private readonly IConnectionProvider _connectionProvider; + + public ContextualConnectionAccess(string connectionString, IConnectionProvider connectionProvider) + { + ConnectionString = connectionString; + _connectionProvider = connectionProvider; + } + + public DbConnection GetConnection() + { + return _connectionProvider.GetConnection(ConnectionString); + } + + public void CloseConnection(DbConnection connection) + { + _connectionProvider.CloseConnection(connection); + } + + public string ConnectionString { get; } + } + } +} diff --git a/src/NHibernate/MultiTenancy/IMultiTenantConnectionProvider.cs b/src/NHibernate/MultiTenancy/IMultiTenantConnectionProvider.cs new file mode 100644 index 00000000000..61de06f3886 --- /dev/null +++ b/src/NHibernate/MultiTenancy/IMultiTenantConnectionProvider.cs @@ -0,0 +1,15 @@ +using NHibernate.Connection; + +namespace NHibernate.MultiTenancy +{ + /// + /// A specialized Connection provider contract used when the application is using multi-tenancy support requiring + /// tenant aware connections. + /// + public interface IMultiTenantConnectionProvider + { + string TenantIdentifier { get; } + + IConnectionAccess GetConnectionAccess(); + } +} diff --git a/src/NHibernate/MultiTenancy/MultiTenancyStrategy.cs b/src/NHibernate/MultiTenancy/MultiTenancyStrategy.cs new file mode 100644 index 00000000000..ce1b2477424 --- /dev/null +++ b/src/NHibernate/MultiTenancy/MultiTenancyStrategy.cs @@ -0,0 +1,25 @@ +namespace NHibernate.MultiTenancy +{ + public enum MultiTenancyStrategy + { + /// + /// No multi-tenancy + /// + None, + +// /// +// /// Multi-tenancy implemented by use of discriminator columns. +// /// +// Discriminator, +// +// /// +// /// Multi-tenancy implemented as separate schemas. +// /// +// Schema, + + /// + /// Multi-tenancy implemented as separate database per tenant. + /// + Database, + } +} diff --git a/src/NHibernate/MultiTenancy/TenantConfiguration.cs b/src/NHibernate/MultiTenancy/TenantConfiguration.cs new file mode 100644 index 00000000000..6591976bc92 --- /dev/null +++ b/src/NHibernate/MultiTenancy/TenantConfiguration.cs @@ -0,0 +1,19 @@ +using NHibernate.Connection; + +namespace NHibernate.MultiTenancy +{ + /// + /// Tenant specific configuration + /// + public class TenantConfiguration + { + public string TenantIdentifier { get; set; } + public IConnectionAccess ConnectionAccess { get; set; } + + public TenantConfiguration(IMultiTenantConnectionProvider tenantConnectionProvider) + { + TenantIdentifier = tenantConnectionProvider.TenantIdentifier; + ConnectionAccess = tenantConnectionProvider.GetConnectionAccess(); + } + } +} diff --git a/src/NHibernate/Util/PropertiesHelper.cs b/src/NHibernate/Util/PropertiesHelper.cs index da80f5b0860..fcd7f350741 100644 --- a/src/NHibernate/Util/PropertiesHelper.cs +++ b/src/NHibernate/Util/PropertiesHelper.cs @@ -55,6 +55,17 @@ public static string GetString(string property, IDictionary prop return value ?? defaultValue; } + public static TEnum GetEnum(string property, IDictionary properties, TEnum defaultValue) where TEnum : struct + { + var enumValue = GetString(property, properties, null); + if (enumValue == null) + { + return defaultValue; + } + + return (TEnum) Enum.Parse(typeof(TEnum), enumValue, false); + } + public static IDictionary ToDictionary(string property, string delim, IDictionary properties) { IDictionary map = new Dictionary(); From 74228685e37171734d94bf0bbc9002d25c088141 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Mon, 8 Apr 2019 17:19:56 +0300 Subject: [PATCH 02/27] Fix serialization --- .../DatabaseStrategyNoDbSpecificFixture.cs | 51 ++++++++++++++ .../DatabaseStrategyNoDbSpecificFixture.cs | 68 ++++++++++++++++--- src/NHibernate/AdoNet/ConnectionManager.cs | 4 +- .../Async/Impl/AbstractSessionImpl.cs | 5 +- .../AbstractMultiTenantConnectionProvider.cs | 4 +- src/NHibernate/Impl/AbstractSessionImpl.cs | 30 ++++---- .../AbstractMultiTenantConnectionProvider.cs | 20 +++--- 7 files changed, 142 insertions(+), 40 deletions(-) diff --git a/src/NHibernate.Test/Async/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs b/src/NHibernate.Test/Async/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs index ad509afe1b1..b8025fad95d 100644 --- a/src/NHibernate.Test/Async/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs +++ b/src/NHibernate.Test/Async/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs @@ -10,7 +10,9 @@ using System; using System.Data.SqlClient; +using System.IO; using System.Linq; +using System.Runtime.Serialization.Formatters.Binary; using NHibernate.Cfg; using NHibernate.Cfg.MappingSchema; using NHibernate.Connection; @@ -19,6 +21,7 @@ using NHibernate.Linq; using NHibernate.Mapping.ByCode; using NHibernate.MultiTenancy; +using NHibernate.Util; using NUnit.Framework; namespace NHibernate.Test.MultiTenancy @@ -36,6 +39,12 @@ protected override void Configure(Configuration configuration) base.Configure(configuration); } + private static void ValidateSqlServerConnectionAppName(ISession s, string tenantId) + { + var builder = new SqlConnectionStringBuilder(s.Connection.ConnectionString); + Assert.That(builder.ApplicationName, Is.EqualTo(tenantId)); + } + [Test] public async Task SecondLevelCacheReusedForSameTenantAsync() { @@ -107,6 +116,46 @@ public async Task QueryCacheSeparationPerTenantAsync() Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(1)); Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(0)); } + + [Test] + public async Task TenantSessionIsSerializableAndCanBeReconnectedAsync() + { + ISession deserializedSession = null; + using (var sesTen1 = OpenTenantSession("tenant1")) + { + var entity = await (sesTen1.Query().WithOptions(x => x.SetCacheable(true)).Where(e => e.Id == _id).SingleOrDefaultAsync()); + sesTen1.Disconnect(); + deserializedSession = SpoofSerialization(sesTen1); + } + + Sfi.Statistics.Clear(); + using (deserializedSession) + { + deserializedSession.Reconnect(); + var entity = await (deserializedSession.GetAsync(_id)); + if (IsSqlServerDialect) + ValidateSqlServerConnectionAppName(deserializedSession, "tenant1"); + } + + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0)); + Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(0)); + } + + private ISession SpoofSerialization(ISession session) + { + var formatter = new BinaryFormatter + { +#if !NETFX + SurrogateSelector = new SerializationHelper.SurrogateSelector() +#endif + }; + MemoryStream stream = new MemoryStream(); + formatter.Serialize(stream, session); + + stream.Position = 0; + + return (ISession) formatter.Deserialize(stream); + } private ISession OpenTenantSession(string tenantId) { @@ -118,6 +167,8 @@ private TenantConfiguration GetTenantConfig(string tenantId) return new TenantConfiguration(new TestTenantConnectionProvider(Sfi, tenantId)); } + private bool IsSqlServerDialect => Sfi.Dialect is MsSql2005Dialect; + #region Test Setup protected override HbmMapping GetMappings() diff --git a/src/NHibernate.Test/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs b/src/NHibernate.Test/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs index 8d66ef3f0e4..e2092d3d0fb 100644 --- a/src/NHibernate.Test/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs +++ b/src/NHibernate.Test/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs @@ -1,6 +1,8 @@ using System; using System.Data.SqlClient; +using System.IO; using System.Linq; +using System.Runtime.Serialization.Formatters.Binary; using NHibernate.Cfg; using NHibernate.Cfg.MappingSchema; using NHibernate.Connection; @@ -9,6 +11,7 @@ using NHibernate.Linq; using NHibernate.Mapping.ByCode; using NHibernate.MultiTenancy; +using NHibernate.Util; using NUnit.Framework; namespace NHibernate.Test.MultiTenancy @@ -44,20 +47,24 @@ public void ShouldThrowWithNoConnectionAccess() [Test] public void DifferentConnectionStringForDifferentTenants() { - if (!(Sfi.Dialect is MsSql2005Dialect)) + if (!IsSqlServerDialect) Assert.Ignore("MSSqlServer specific test"); using(var session1 = OpenTenantSession("tenant1")) using (var session2 = OpenTenantSession("tenant2")) { Assert.That(session1.Connection.ConnectionString, Is.Not.EqualTo(session2.Connection.ConnectionString)); - var builder1 = new SqlConnectionStringBuilder(session1.Connection.ConnectionString); - var builder2 = new SqlConnectionStringBuilder(session2.Connection.ConnectionString); - Assert.That(builder1.ApplicationName, Is.EqualTo("tenant1")); - Assert.That(builder2.ApplicationName, Is.EqualTo("tenant2")); + ValidateSqlServerConnectionAppName(session1, "tenant1"); + ValidateSqlServerConnectionAppName(session2, "tenant2"); } } + private static void ValidateSqlServerConnectionAppName(ISession s, string tenantId) + { + var builder = new SqlConnectionStringBuilder(s.Connection.ConnectionString); + Assert.That(builder.ApplicationName, Is.EqualTo(tenantId)); + } + [Test] public void SecondLevelCacheReusedForSameTenant() { @@ -129,6 +136,46 @@ public void QueryCacheSeparationPerTenant() Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(1)); Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(0)); } + + [Test] + public void TenantSessionIsSerializableAndCanBeReconnected() + { + ISession deserializedSession = null; + using (var sesTen1 = OpenTenantSession("tenant1")) + { + var entity = sesTen1.Query().WithOptions(x => x.SetCacheable(true)).Where(e => e.Id == _id).SingleOrDefault(); + sesTen1.Disconnect(); + deserializedSession = SpoofSerialization(sesTen1); + } + + Sfi.Statistics.Clear(); + using (deserializedSession) + { + deserializedSession.Reconnect(); + var entity = deserializedSession.Get(_id); + if (IsSqlServerDialect) + ValidateSqlServerConnectionAppName(deserializedSession, "tenant1"); + } + + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0)); + Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(0)); + } + + private ISession SpoofSerialization(ISession session) + { + var formatter = new BinaryFormatter + { +#if !NETFX + SurrogateSelector = new SerializationHelper.SurrogateSelector() +#endif + }; + MemoryStream stream = new MemoryStream(); + formatter.Serialize(stream, session); + + stream.Position = 0; + + return (ISession) formatter.Deserialize(stream); + } private ISession OpenTenantSession(string tenantId) { @@ -140,6 +187,8 @@ private TenantConfiguration GetTenantConfig(string tenantId) return new TenantConfiguration(new TestTenantConnectionProvider(Sfi, tenantId)); } + private bool IsSqlServerDialect => Sfi.Dialect is MsSql2005Dialect; + #region Test Setup protected override HbmMapping GetMappings() @@ -193,6 +242,8 @@ protected override void OnSetUp() #endregion Test Setup } + + [Serializable] class Entity { public virtual Guid Id { get; set; } @@ -216,16 +267,17 @@ public IConnectionAccess GetConnectionAccess() } } + [Serializable] public class TestTenantConnectionProvider : AbstractMultiTenantConnectionProvider { public TestTenantConnectionProvider(ISessionFactoryImplementor sfi, string tenantId) { TenantIdentifier = tenantId; - ConnectionProvider = sfi.ConnectionProvider; + SessionFactory = sfi; TenantConnectionString = sfi.ConnectionProvider.GetConnectionString(); if (sfi.Dialect is MsSql2005Dialect) { - var stringBuilder = new SqlConnectionStringBuilder(ConnectionProvider.GetConnectionString()); + var stringBuilder = new SqlConnectionStringBuilder(sfi.ConnectionProvider.GetConnectionString()); stringBuilder.ApplicationName = tenantId; TenantConnectionString = stringBuilder.ToString(); } @@ -235,6 +287,6 @@ public TestTenantConnectionProvider(ISessionFactoryImplementor sfi, string tenan public override string TenantIdentifier { get; } - protected override IConnectionProvider ConnectionProvider { get; } + protected override ISessionFactoryImplementor SessionFactory { get; } } } diff --git a/src/NHibernate/AdoNet/ConnectionManager.cs b/src/NHibernate/AdoNet/ConnectionManager.cs index b9457ec8209..b607f433502 100644 --- a/src/NHibernate/AdoNet/ConnectionManager.cs +++ b/src/NHibernate/AdoNet/ConnectionManager.cs @@ -86,7 +86,7 @@ public ConnectionManager( #pragma warning restore 618 { - _connectionAccess = connectionAccess; + _connectionAccess = connectionAccess ?? throw new ArgumentNullException(nameof(connectionAccess)); } //Since 5.3 @@ -380,6 +380,7 @@ private ConnectionManager(SerializationInfo info, StreamingContext context) _connectionReleaseMode = (ConnectionReleaseMode)info.GetValue("connectionReleaseMode", typeof(ConnectionReleaseMode)); _interceptor = (IInterceptor)info.GetValue("interceptor", typeof(IInterceptor)); + _connectionAccess = (IConnectionAccess) info.GetValue("connectionAccess", typeof(IConnectionAccess)); } [SecurityCritical] @@ -389,6 +390,7 @@ public void GetObjectData(SerializationInfo info, StreamingContext context) info.AddValue("session", Session, typeof(ISessionImplementor)); info.AddValue("connectionReleaseMode", _connectionReleaseMode, typeof(ConnectionReleaseMode)); info.AddValue("interceptor", _interceptor, typeof(IInterceptor)); + info.AddValue("connectionAccess", _connectionAccess, typeof(IConnectionAccess)); } #endregion diff --git a/src/NHibernate/Async/Impl/AbstractSessionImpl.cs b/src/NHibernate/Async/Impl/AbstractSessionImpl.cs index 9acba2eeffb..0c5f4f58e62 100644 --- a/src/NHibernate/Async/Impl/AbstractSessionImpl.cs +++ b/src/NHibernate/Async/Impl/AbstractSessionImpl.cs @@ -220,8 +220,7 @@ protected async Task AfterOperationAsync(bool success, CancellationToken cancell public abstract Task ExecuteUpdateAsync(IQueryExpression queryExpression, QueryParameters queryParameters, CancellationToken cancellationToken); } - - partial class NonContextualConnectionAccess : IConnectionAccess + partial class NonContextualConnectionAccess : IConnectionAccess { public Task GetConnectionAsync(CancellationToken cancellationToken) @@ -230,7 +229,7 @@ public Task GetConnectionAsync(CancellationToken cancellationToken { return Task.FromCanceled(cancellationToken); } - return _connectionProvider.GetConnectionAsync(cancellationToken); + return _factory.ConnectionProvider.GetConnectionAsync(cancellationToken); } } } diff --git a/src/NHibernate/Async/MultiTenancy/AbstractMultiTenantConnectionProvider.cs b/src/NHibernate/Async/MultiTenancy/AbstractMultiTenantConnectionProvider.cs index e46dae344c6..96c325c7d3e 100644 --- a/src/NHibernate/Async/MultiTenancy/AbstractMultiTenantConnectionProvider.cs +++ b/src/NHibernate/Async/MultiTenancy/AbstractMultiTenantConnectionProvider.cs @@ -11,6 +11,7 @@ using System; using System.Data.Common; using NHibernate.Connection; +using NHibernate.Engine; using NHibernate.Util; namespace NHibernate.MultiTenancy @@ -19,7 +20,6 @@ namespace NHibernate.MultiTenancy using System.Threading; public abstract partial class AbstractMultiTenantConnectionProvider : IMultiTenantConnectionProvider { - partial class ContextualConnectionAccess : IConnectionAccess { @@ -29,7 +29,7 @@ public Task GetConnectionAsync(CancellationToken cancellationToken { return Task.FromCanceled(cancellationToken); } - return _connectionProvider.GetConnectionAsync(ConnectionString, cancellationToken); + return _factory.ConnectionProvider.GetConnectionAsync(ConnectionString, cancellationToken); } } } diff --git a/src/NHibernate/Impl/AbstractSessionImpl.cs b/src/NHibernate/Impl/AbstractSessionImpl.cs index a3452e41018..3bc59ef5717 100644 --- a/src/NHibernate/Impl/AbstractSessionImpl.cs +++ b/src/NHibernate/Impl/AbstractSessionImpl.cs @@ -37,8 +37,6 @@ public abstract partial class AbstractSessionImpl : ISessionImplementor [NonSerialized] private IQueryBatch _futureMultiBatch; - private readonly TenantConfiguration _tenantConfiguration; - private bool closed; /// Get the current NHibernate transaction. @@ -87,11 +85,9 @@ protected internal AbstractSessionImpl(ISessionFactoryImplementor factory, ISess _flushMode = options.InitialSessionFlushMode; Interceptor = options.SessionInterceptor ?? EmptyInterceptor.Instance; - if (options is ISessionCreationOptionsWithMultiTenancy multiTenancy) - { - _tenantConfiguration = multiTenancy.TenantConfiguration; - ValidateTenantConfiguration(factory, _tenantConfiguration); - } + TenantConfiguration tenantConfiguration = options is ISessionCreationOptionsWithMultiTenancy multiTenancy ? multiTenancy.TenantConfiguration : null; + ValidateTenantConfiguration(factory, tenantConfiguration); + TenantIdentifier = tenantConfiguration?.TenantIdentifier; if (options is ISharedSessionCreationOptions sharedOptions && sharedOptions.IsTransactionCoordinatorShared) { @@ -110,7 +106,7 @@ protected internal AbstractSessionImpl(ISessionFactoryImplementor factory, ISess options.UserSuppliedConnection, options.SessionConnectionReleaseMode, Interceptor, - _tenantConfiguration?.ConnectionAccess ?? new NonContextualConnectionAccess(_factory.ConnectionProvider), + tenantConfiguration?.ConnectionAccess ?? new NonContextualConnectionAccess(_factory), options.ShouldAutoJoinTransaction); } } @@ -492,7 +488,7 @@ protected bool IsAlreadyDisposed /// public virtual bool TransactionInProgress => ConnectionManager.IsInActiveTransaction; - public string TenantIdentifier => _tenantConfiguration?.TenantIdentifier; + public string TenantIdentifier { get; } #endregion @@ -698,27 +694,27 @@ public virtual IQueryBatch CreateQueryBatch() return new QueryBatch(this, false); } } -[Serializable] - partial class NonContextualConnectionAccess : IConnectionAccess + [Serializable] + partial class NonContextualConnectionAccess : IConnectionAccess { - private readonly IConnectionProvider _connectionProvider; + private readonly ISessionFactoryImplementor _factory; - public NonContextualConnectionAccess(IConnectionProvider connectionProvider) + public NonContextualConnectionAccess(ISessionFactoryImplementor factory) { - _connectionProvider = connectionProvider; + _factory = factory; } public DbConnection GetConnection() { - return _connectionProvider.GetConnection(); + return _factory.ConnectionProvider.GetConnection(); } public void CloseConnection(DbConnection connection) { - _connectionProvider.CloseConnection(connection); + _factory.ConnectionProvider.CloseConnection(connection); } - public string ConnectionString => _connectionProvider.GetConnectionString(); + public string ConnectionString => _factory.ConnectionProvider.GetConnectionString(); } } diff --git a/src/NHibernate/MultiTenancy/AbstractMultiTenantConnectionProvider.cs b/src/NHibernate/MultiTenancy/AbstractMultiTenantConnectionProvider.cs index 3ecdfd74c6a..96fd40714e8 100644 --- a/src/NHibernate/MultiTenancy/AbstractMultiTenantConnectionProvider.cs +++ b/src/NHibernate/MultiTenancy/AbstractMultiTenantConnectionProvider.cs @@ -1,6 +1,7 @@ using System; using System.Data.Common; using NHibernate.Connection; +using NHibernate.Engine; using NHibernate.Util; namespace NHibernate.MultiTenancy @@ -8,6 +9,7 @@ namespace NHibernate.MultiTenancy /// /// Base implementation for multi-tenancy strategy /// + [Serializable] public abstract partial class AbstractMultiTenantConnectionProvider : IMultiTenantConnectionProvider { protected abstract string TenantConnectionString { get; } @@ -16,31 +18,31 @@ public abstract partial class AbstractMultiTenantConnectionProvider : IMultiTena public IConnectionAccess GetConnectionAccess() { //TODO 6.0: Remove check - ReflectHelper.CastOrThrow(ConnectionProvider, $"multi-tenancy. For custom connection provider please implement IMultiTenantConnectionProvider directly for '{GetType().Name}' and do not use {nameof(AbstractMultiTenantConnectionProvider)} as base class."); - return new ContextualConnectionAccess(TenantConnectionString, ConnectionProvider); + ReflectHelper.CastOrThrow(SessionFactory.ConnectionProvider, $"multi-tenancy. For custom connection provider please implement IMultiTenantConnectionProvider directly for '{GetType().Name}' and do not use {nameof(AbstractMultiTenantConnectionProvider)} as base class."); + return new ContextualConnectionAccess(TenantConnectionString, SessionFactory); } - protected abstract IConnectionProvider ConnectionProvider { get; } -[Serializable] + protected abstract ISessionFactoryImplementor SessionFactory { get; } + [Serializable] partial class ContextualConnectionAccess : IConnectionAccess { - private readonly IConnectionProvider _connectionProvider; + private readonly ISessionFactoryImplementor _factory; - public ContextualConnectionAccess(string connectionString, IConnectionProvider connectionProvider) + public ContextualConnectionAccess(string connectionString, ISessionFactoryImplementor factory) { + _factory = factory; ConnectionString = connectionString; - _connectionProvider = connectionProvider; } public DbConnection GetConnection() { - return _connectionProvider.GetConnection(ConnectionString); + return _factory.ConnectionProvider.GetConnection(ConnectionString); } public void CloseConnection(DbConnection connection) { - _connectionProvider.CloseConnection(connection); + _factory.ConnectionProvider.CloseConnection(connection); } public string ConnectionString { get; } From 36d6d9886b483e052c08f1a11fe8881c5848ec2c Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Mon, 8 Apr 2019 21:44:43 +0300 Subject: [PATCH 03/27] Fix ODBC test --- .../Async/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs | 3 ++- .../MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/NHibernate.Test/Async/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs b/src/NHibernate.Test/Async/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs index b8025fad95d..49a25142da4 100644 --- a/src/NHibernate.Test/Async/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs +++ b/src/NHibernate.Test/Async/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs @@ -17,6 +17,7 @@ using NHibernate.Cfg.MappingSchema; using NHibernate.Connection; using NHibernate.Dialect; +using NHibernate.Driver; using NHibernate.Engine; using NHibernate.Linq; using NHibernate.Mapping.ByCode; @@ -167,7 +168,7 @@ private TenantConfiguration GetTenantConfig(string tenantId) return new TenantConfiguration(new TestTenantConnectionProvider(Sfi, tenantId)); } - private bool IsSqlServerDialect => Sfi.Dialect is MsSql2005Dialect; + private bool IsSqlServerDialect => Sfi.Dialect is MsSql2000Dialect && !(Sfi.ConnectionProvider.Driver is OdbcDriver); #region Test Setup diff --git a/src/NHibernate.Test/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs b/src/NHibernate.Test/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs index e2092d3d0fb..8d46a7ba17d 100644 --- a/src/NHibernate.Test/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs +++ b/src/NHibernate.Test/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs @@ -7,6 +7,7 @@ using NHibernate.Cfg.MappingSchema; using NHibernate.Connection; using NHibernate.Dialect; +using NHibernate.Driver; using NHibernate.Engine; using NHibernate.Linq; using NHibernate.Mapping.ByCode; @@ -187,7 +188,7 @@ private TenantConfiguration GetTenantConfig(string tenantId) return new TenantConfiguration(new TestTenantConnectionProvider(Sfi, tenantId)); } - private bool IsSqlServerDialect => Sfi.Dialect is MsSql2005Dialect; + private bool IsSqlServerDialect => Sfi.Dialect is MsSql2000Dialect && !(Sfi.ConnectionProvider.Driver is OdbcDriver); #region Test Setup From 67bac348dee16440c46dac2d24e171744ba63108 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Tue, 9 Apr 2019 07:49:06 +0300 Subject: [PATCH 04/27] Small clean up and refactoring --- .../DatabaseStrategyNoDbSpecificFixture.cs | 2 +- .../DatabaseStrategyNoDbSpecificFixture.cs | 6 +++--- src/NHibernate/Cache/CacheKey.cs | 1 + src/NHibernate/Connection/IConnectionAccess.cs | 2 +- src/NHibernate/Impl/AbstractSessionImpl.cs | 12 ++++++++---- 5 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/NHibernate.Test/Async/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs b/src/NHibernate.Test/Async/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs index 49a25142da4..4ff8038b5e6 100644 --- a/src/NHibernate.Test/Async/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs +++ b/src/NHibernate.Test/Async/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs @@ -165,7 +165,7 @@ private ISession OpenTenantSession(string tenantId) private TenantConfiguration GetTenantConfig(string tenantId) { - return new TenantConfiguration(new TestTenantConnectionProvider(Sfi, tenantId)); + return new TenantConfiguration(new TestTenantConnectionProvider(Sfi, tenantId, IsSqlServerDialect)); } private bool IsSqlServerDialect => Sfi.Dialect is MsSql2000Dialect && !(Sfi.ConnectionProvider.Driver is OdbcDriver); diff --git a/src/NHibernate.Test/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs b/src/NHibernate.Test/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs index 8d46a7ba17d..c0915987269 100644 --- a/src/NHibernate.Test/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs +++ b/src/NHibernate.Test/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs @@ -185,7 +185,7 @@ private ISession OpenTenantSession(string tenantId) private TenantConfiguration GetTenantConfig(string tenantId) { - return new TenantConfiguration(new TestTenantConnectionProvider(Sfi, tenantId)); + return new TenantConfiguration(new TestTenantConnectionProvider(Sfi, tenantId, IsSqlServerDialect)); } private bool IsSqlServerDialect => Sfi.Dialect is MsSql2000Dialect && !(Sfi.ConnectionProvider.Driver is OdbcDriver); @@ -271,12 +271,12 @@ public IConnectionAccess GetConnectionAccess() [Serializable] public class TestTenantConnectionProvider : AbstractMultiTenantConnectionProvider { - public TestTenantConnectionProvider(ISessionFactoryImplementor sfi, string tenantId) + public TestTenantConnectionProvider(ISessionFactoryImplementor sfi, string tenantId, bool isSqlServerDialect) { TenantIdentifier = tenantId; SessionFactory = sfi; TenantConnectionString = sfi.ConnectionProvider.GetConnectionString(); - if (sfi.Dialect is MsSql2005Dialect) + if (isSqlServerDialect) { var stringBuilder = new SqlConnectionStringBuilder(sfi.ConnectionProvider.GetConnectionString()); stringBuilder.ApplicationName = tenantId; diff --git a/src/NHibernate/Cache/CacheKey.cs b/src/NHibernate/Cache/CacheKey.cs index 52aab1a35dc..0187c3879e0 100644 --- a/src/NHibernate/Cache/CacheKey.cs +++ b/src/NHibernate/Cache/CacheKey.cs @@ -43,6 +43,7 @@ public CacheKey(object id, IType type, string entityOrRoleName, ISessionFactoryI _hashCode = GenerateHashCode(); } + //Since 5.3 [Obsolete("Use constructor with tenantIdentifier")] public CacheKey(object id, IType type, string entityOrRoleName, ISessionFactoryImplementor factory) { diff --git a/src/NHibernate/Connection/IConnectionAccess.cs b/src/NHibernate/Connection/IConnectionAccess.cs index 7b92c45327b..8e80685830a 100644 --- a/src/NHibernate/Connection/IConnectionAccess.cs +++ b/src/NHibernate/Connection/IConnectionAccess.cs @@ -12,7 +12,7 @@ public partial interface IConnectionAccess //ObtainConnection in hibernate DbConnection GetConnection(); - //Note: ReleaseConnection in hibernate + //ReleaseConnection in hibernate void CloseConnection(DbConnection conn); string ConnectionString { get; } diff --git a/src/NHibernate/Impl/AbstractSessionImpl.cs b/src/NHibernate/Impl/AbstractSessionImpl.cs index 3bc59ef5717..61308658ffd 100644 --- a/src/NHibernate/Impl/AbstractSessionImpl.cs +++ b/src/NHibernate/Impl/AbstractSessionImpl.cs @@ -59,10 +59,12 @@ public ITransactionContext TransactionContext internal AbstractSessionImpl() { } - private void ValidateTenantConfiguration(ISessionFactoryImplementor factory, TenantConfiguration tenantConfiguration) + private TenantConfiguration ValidateTenantConfiguration(ISessionFactoryImplementor factory, ISessionCreationOptions options) { if (factory.Settings.MultiTenancyStrategy == MultiTenancyStrategy.None) - return; + return null; + + var tenantConfiguration = ReflectHelper.CastOrThrow(options, "multi-tenancy").TenantConfiguration; if (string.IsNullOrEmpty(tenantConfiguration?.TenantIdentifier)) { @@ -73,6 +75,8 @@ private void ValidateTenantConfiguration(ISessionFactoryImplementor factory, Ten { throw new ArgumentException($"Tenant configuration with ConnectionAccess defined is required for {factory.Settings.MultiTenancyStrategy} multi-tenancy strategy."); } + + return tenantConfiguration; } protected internal AbstractSessionImpl(ISessionFactoryImplementor factory, ISessionCreationOptions options) @@ -85,8 +89,8 @@ protected internal AbstractSessionImpl(ISessionFactoryImplementor factory, ISess _flushMode = options.InitialSessionFlushMode; Interceptor = options.SessionInterceptor ?? EmptyInterceptor.Instance; - TenantConfiguration tenantConfiguration = options is ISessionCreationOptionsWithMultiTenancy multiTenancy ? multiTenancy.TenantConfiguration : null; - ValidateTenantConfiguration(factory, tenantConfiguration); + TenantConfiguration tenantConfiguration = ValidateTenantConfiguration(factory, options); + TenantIdentifier = tenantConfiguration?.TenantIdentifier; if (options is ISharedSessionCreationOptions sharedOptions && sharedOptions.IsTransactionCoordinatorShared) From 674ab71c1e74879963d9119fff09c60b8516ef4c Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Tue, 9 Apr 2019 11:58:23 +0300 Subject: [PATCH 05/27] Tenant aware SchemaExport --- .../DatabaseStrategyNoDbSpecificFixture.cs | 6 + .../DatabaseStrategyNoDbSpecificFixture.cs | 6 + src/NHibernate.Test/TestCase.cs | 19 +- .../Async/Tool/hbm2ddl/SchemaExport.cs | 175 +++++++++++++++--- src/NHibernate/Tool/hbm2ddl/SchemaExport.cs | 136 +++++++++++--- 5 files changed, 289 insertions(+), 53 deletions(-) diff --git a/src/NHibernate.Test/Async/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs b/src/NHibernate.Test/Async/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs index 4ff8038b5e6..c5bdb294070 100644 --- a/src/NHibernate.Test/Async/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs +++ b/src/NHibernate.Test/Async/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs @@ -9,6 +9,7 @@ using System; +using System.Data.Common; using System.Data.SqlClient; using System.IO; using System.Linq; @@ -187,6 +188,11 @@ protected override HbmMapping GetMappings() return mapper.CompileMappingForAllExplicitlyAddedEntities(); } + protected override DbConnection OpenConnectionForSchemaExport() + { + return GetTenantConfig("defaultTenant").ConnectionAccess.GetConnection(); + } + protected override ISession OpenSession() { return OpenTenantSession("defaultTenant"); diff --git a/src/NHibernate.Test/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs b/src/NHibernate.Test/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs index c0915987269..e28f1cadea2 100644 --- a/src/NHibernate.Test/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs +++ b/src/NHibernate.Test/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs @@ -1,4 +1,5 @@ using System; +using System.Data.Common; using System.Data.SqlClient; using System.IO; using System.Linq; @@ -207,6 +208,11 @@ protected override HbmMapping GetMappings() return mapper.CompileMappingForAllExplicitlyAddedEntities(); } + protected override DbConnection OpenConnectionForSchemaExport() + { + return GetTenantConfig("defaultTenant").ConnectionAccess.GetConnection(); + } + protected override ISession OpenSession() { return OpenTenantSession("defaultTenant"); diff --git a/src/NHibernate.Test/TestCase.cs b/src/NHibernate.Test/TestCase.cs index bee3ebeb9fc..fb66557e891 100644 --- a/src/NHibernate.Test/TestCase.cs +++ b/src/NHibernate.Test/TestCase.cs @@ -2,6 +2,7 @@ using System.Collections; using System.Collections.Generic; using System.Data; +using System.Data.Common; using System.Reflection; using log4net; using NHibernate.Cfg; @@ -291,15 +292,17 @@ protected virtual void AddMappings(Configuration configuration) protected virtual void CreateSchema() { - SchemaExport.Create(OutputDdl, true); + using (var optionalConnection = OpenConnectionForSchemaExport()) + SchemaExport.Create(OutputDdl, true, optionalConnection); } protected virtual void DropSchema() { - DropSchema(OutputDdl, SchemaExport, Sfi); + using (var optionalConnection = OpenConnectionForSchemaExport()) + DropSchema(OutputDdl, SchemaExport, Sfi, optionalConnection); } - public static void DropSchema(bool useStdOut, SchemaExport export, ISessionFactoryImplementor sfi) + public static void DropSchema(bool useStdOut, SchemaExport export, ISessionFactoryImplementor sfi, DbConnection optionalConnection = null) { if (sfi?.ConnectionProvider.Driver is FirebirdClientDriver fbDriver) { @@ -312,7 +315,15 @@ public static void DropSchema(bool useStdOut, SchemaExport export, ISessionFacto fbDriver.ClearPool(null); } - export.Drop(useStdOut, true); + export.Drop(useStdOut, true, optionalConnection); + } + + /// + /// Specific connection is required only for Database multi-tenancy. In other cases can be null + /// + protected virtual DbConnection OpenConnectionForSchemaExport() + { + return null; } protected virtual DebugSessionFactory BuildSessionFactory() diff --git a/src/NHibernate/Async/Tool/hbm2ddl/SchemaExport.cs b/src/NHibernate/Async/Tool/hbm2ddl/SchemaExport.cs index 046f3aebda5..74a6f5595b7 100644 --- a/src/NHibernate/Async/Tool/hbm2ddl/SchemaExport.cs +++ b/src/NHibernate/Async/Tool/hbm2ddl/SchemaExport.cs @@ -17,6 +17,7 @@ using NHibernate.AdoNet.Util; using NHibernate.Cfg; using NHibernate.Connection; +using NHibernate.MultiTenancy; using NHibernate.Util; using Environment=NHibernate.Cfg.Environment; @@ -46,9 +47,11 @@ public partial class SchemaExport dropSQL = cfg.GenerateDropSchemaScript(dialect); createSQL = cfg.GenerateSchemaCreationScript(dialect); formatter = (PropertiesHelper.GetBoolean(Environment.FormatSql, configProperties, true) ? FormatStyle.Ddl : FormatStyle.None).Formatter; + _requireTenantConnection = PropertiesHelper.GetEnum(Environment.MultiTenant, configProperties, MultiTenancyStrategy.None) == MultiTenancyStrategy.Database; wasInitialized = true; } + //TODO 6.0: Remove (replaced by method with optional connection parameter) /// /// Run the schema creation script /// @@ -65,9 +68,38 @@ public partial class SchemaExport { return Task.FromCanceled(cancellationToken); } - return ExecuteAsync(useStdOut, execute, false, cancellationToken); + return CreateAsync(useStdOut, execute, null, cancellationToken); } + /// + /// Run the schema creation script + /// + /// if the ddl should be outputted in the Console. + /// if the ddl should be executed against the Database. + /// Optional explicit connection. Required for multi-tenancy. + /// Must be an opened connection. The method doesn't close the connection. + /// A cancellation token that can be used to cancel the work + /// + /// This is a convenience method that calls and sets + /// the justDrop parameter to false. + /// + public Task CreateAsync(bool useStdOut, bool execute, DbConnection connection = null, CancellationToken cancellationToken = default(CancellationToken)) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + try + { + return InitConnectionAndExecuteAsync(GetAction(useStdOut), execute, false, connection, null, cancellationToken); + } + catch (Exception ex) + { + return Task.FromException(ex); + } + } + + //TODO 6.0: Remove (replaced by method with optional connection parameter) /// /// Run the schema creation script /// @@ -84,9 +116,31 @@ public partial class SchemaExport { return Task.FromCanceled(cancellationToken); } - return ExecuteAsync(scriptAction, execute, false, cancellationToken); + return CreateAsync(scriptAction, execute, null, cancellationToken); } + /// + /// Run the schema creation script + /// + /// an action that will be called for each line of the generated ddl. + /// if the ddl should be executed against the Database. + /// Optional explicit connection. Required for multi-tenancy. + /// Must be an opened connection. The method doesn't close the connection. + /// A cancellation token that can be used to cancel the work + /// + /// This is a convenience method that calls and sets + /// the justDrop parameter to false. + /// + public Task CreateAsync(Action scriptAction, bool execute, DbConnection connection = null, CancellationToken cancellationToken = default(CancellationToken)) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + return InitConnectionAndExecuteAsync(scriptAction, execute, false, connection, null, cancellationToken); + } + + //TODO 6.0: Remove (replaced by method with optional connection parameter) /// /// Run the schema creation script /// @@ -103,9 +157,31 @@ public partial class SchemaExport { return Task.FromCanceled(cancellationToken); } - return ExecuteAsync(null, execute, false, exportOutput, cancellationToken); + return CreateAsync(exportOutput, execute, null, cancellationToken); } + /// + /// Run the schema creation script + /// + /// if non-null, the ddl will be written to this TextWriter. + /// if the ddl should be executed against the Database. + /// Optional explicit connection. Required for multi-tenancy. + /// Must be an opened connection. The method doesn't close the connection. + /// A cancellation token that can be used to cancel the work + /// + /// This is a convenience method that calls and sets + /// the justDrop parameter to false. + /// + public Task CreateAsync(TextWriter exportOutput, bool execute, DbConnection connection = null, CancellationToken cancellationToken = default(CancellationToken)) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + return InitConnectionAndExecuteAsync(null, execute, false, connection, exportOutput, cancellationToken); + } + + //TODO 6.0: Remove (replaced by method with optional connection parameter) /// /// Run the drop schema script /// @@ -122,9 +198,38 @@ public partial class SchemaExport { return Task.FromCanceled(cancellationToken); } - return ExecuteAsync(useStdOut, execute, true, cancellationToken); + return DropAsync(useStdOut, execute, null, cancellationToken); + } + + /// + /// Run the drop schema script + /// + /// if the ddl should be outputted in the Console. + /// if the ddl should be executed against the Database. + /// Optional explicit connection. Required for multi-tenancy. + /// Must be an opened connection. The method doesn't close the connection. + /// A cancellation token that can be used to cancel the work + /// + /// This is a convenience method that calls and sets + /// the justDrop parameter to true. + /// + public Task DropAsync(bool useStdOut, bool execute, DbConnection connection = null, CancellationToken cancellationToken = default(CancellationToken)) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + try + { + return InitConnectionAndExecuteAsync(GetAction(useStdOut), execute, true, connection, null, cancellationToken); + } + catch (Exception ex) + { + return Task.FromException(ex); + } } + //TODO 6.0: Remove (replaced by method with optional connection parameter) /// /// Run the drop schema script /// @@ -141,7 +246,28 @@ public partial class SchemaExport { return Task.FromCanceled(cancellationToken); } - return ExecuteAsync(null, execute, true, exportOutput, cancellationToken); + return DropAsync(exportOutput, execute, null, cancellationToken); + } + + /// + /// Run the drop schema script + /// + /// if non-null, the ddl will be written to this TextWriter. + /// if the ddl should be executed against the Database. + /// Optional explicit connection. Required for multi-tenancy. + /// Must be an opened connection. The method doesn't close the connection. + /// A cancellation token that can be used to cancel the work + /// + /// This is a convenience method that calls and sets + /// the justDrop parameter to true. + /// + public Task DropAsync(TextWriter exportOutput, bool execute, DbConnection connection = null, CancellationToken cancellationToken = default(CancellationToken)) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + return InitConnectionAndExecuteAsync(null, execute, true, connection, exportOutput, cancellationToken); } private async Task ExecuteInitializedAsync(Action scriptAction, bool execute, bool throwOnError, TextWriter exportOutput, @@ -228,14 +354,7 @@ public Task ExecuteAsync(bool useStdOut, bool execute, bool justDrop, DbConnecti } try { - if (useStdOut) - { - return ExecuteAsync(Console.WriteLine, execute, justDrop, connection, exportOutput, cancellationToken); - } - else - { - return ExecuteAsync(null, execute, justDrop, connection, exportOutput, cancellationToken); - } + return ExecuteAsync(GetAction(useStdOut), execute, justDrop, connection, exportOutput, cancellationToken); } catch (Exception ex) { @@ -319,14 +438,7 @@ public async Task ExecuteAsync(Action scriptAction, bool execute, bool j } try { - if (useStdOut) - { - return ExecuteAsync(Console.WriteLine, execute, justDrop, cancellationToken); - } - else - { - return ExecuteAsync(null, execute, justDrop, cancellationToken); - } + return InitConnectionAndExecuteAsync(GetAction(useStdOut), execute, justDrop, null, null, cancellationToken); } catch (Exception ex) { @@ -343,11 +455,19 @@ public async Task ExecuteAsync(Action scriptAction, bool execute, bool j return ExecuteAsync(scriptAction, execute, justDrop, null, cancellationToken); } - public async Task ExecuteAsync(Action scriptAction, bool execute, bool justDrop, TextWriter exportOutput, CancellationToken cancellationToken = default(CancellationToken)) + public Task ExecuteAsync(Action scriptAction, bool execute, bool justDrop, TextWriter exportOutput, CancellationToken cancellationToken = default(CancellationToken)) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + return InitConnectionAndExecuteAsync(scriptAction, execute, justDrop, null, exportOutput, cancellationToken); + } + + private async Task InitConnectionAndExecuteAsync(Action scriptAction, bool execute, bool justDrop, DbConnection connection, TextWriter exportOutput, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); await (InitializeAsync(cancellationToken)).ConfigureAwait(false); - DbConnection connection = null; TextWriter fileOutput = exportOutput; IConnectionProvider connectionProvider = null; @@ -358,8 +478,13 @@ public async Task ExecuteAsync(Action scriptAction, bool execute, bool j fileOutput = new StreamWriter(outputFile); } - if (execute) + if (execute && connection == null) { + if (_requireTenantConnection) + { + throw new ArgumentException("When Database multi-tenancy is enabled you need to provide explicit connection. Please use overload with connection parameter."); + } + var props = new Dictionary(); foreach (var de in dialect.DefaultProperties) { @@ -393,7 +518,7 @@ public async Task ExecuteAsync(Action scriptAction, bool execute, bool j } finally { - if (connection != null) + if (connectionProvider != null) { connectionProvider.CloseConnection(connection); connectionProvider.Dispose(); diff --git a/src/NHibernate/Tool/hbm2ddl/SchemaExport.cs b/src/NHibernate/Tool/hbm2ddl/SchemaExport.cs index 2fe8c652109..0a203a272c6 100644 --- a/src/NHibernate/Tool/hbm2ddl/SchemaExport.cs +++ b/src/NHibernate/Tool/hbm2ddl/SchemaExport.cs @@ -7,6 +7,7 @@ using NHibernate.AdoNet.Util; using NHibernate.Cfg; using NHibernate.Connection; +using NHibernate.MultiTenancy; using NHibernate.Util; using Environment=NHibernate.Cfg.Environment; @@ -31,6 +32,7 @@ public partial class SchemaExport private IFormatter formatter; private string delimiter; private string outputFile; + private bool _requireTenantConnection; /// /// Create a schema exported for a given Configuration @@ -68,6 +70,7 @@ private void Initialize() dropSQL = cfg.GenerateDropSchemaScript(dialect); createSQL = cfg.GenerateSchemaCreationScript(dialect); formatter = (PropertiesHelper.GetBoolean(Environment.FormatSql, configProperties, true) ? FormatStyle.Ddl : FormatStyle.None).Formatter; + _requireTenantConnection = PropertiesHelper.GetEnum(Environment.MultiTenant, configProperties, MultiTenancyStrategy.None) == MultiTenancyStrategy.Database; wasInitialized = true; } @@ -93,6 +96,7 @@ public SchemaExport SetDelimiter(string delimiter) return this; } + //TODO 6.0: Remove (replaced by method with optional connection parameter) /// /// Run the schema creation script /// @@ -104,9 +108,26 @@ public SchemaExport SetDelimiter(string delimiter) /// public void Create(bool useStdOut, bool execute) { - Execute(useStdOut, execute, false); + Create(useStdOut, execute, null); } + /// + /// Run the schema creation script + /// + /// if the ddl should be outputted in the Console. + /// if the ddl should be executed against the Database. + /// Optional explicit connection. Required for multi-tenancy. + /// Must be an opened connection. The method doesn't close the connection. + /// + /// This is a convenience method that calls and sets + /// the justDrop parameter to false. + /// + public void Create(bool useStdOut, bool execute, DbConnection connection = null) + { + InitConnectionAndExecute(GetAction(useStdOut), execute, false, connection, null); + } + + //TODO 6.0: Remove (replaced by method with optional connection parameter) /// /// Run the schema creation script /// @@ -118,9 +139,26 @@ public void Create(bool useStdOut, bool execute) /// public void Create(Action scriptAction, bool execute) { - Execute(scriptAction, execute, false); + Create(scriptAction, execute, null); + } + + /// + /// Run the schema creation script + /// + /// an action that will be called for each line of the generated ddl. + /// if the ddl should be executed against the Database. + /// Optional explicit connection. Required for multi-tenancy. + /// Must be an opened connection. The method doesn't close the connection. + /// + /// This is a convenience method that calls and sets + /// the justDrop parameter to false. + /// + public void Create(Action scriptAction, bool execute, DbConnection connection = null) + { + InitConnectionAndExecute(scriptAction, execute, false, connection, null); } + //TODO 6.0: Remove (replaced by method with optional connection parameter) /// /// Run the schema creation script /// @@ -132,9 +170,26 @@ public void Create(Action scriptAction, bool execute) /// public void Create(TextWriter exportOutput, bool execute) { - Execute(null, execute, false, exportOutput); + Create(exportOutput, execute, null); + } + + /// + /// Run the schema creation script + /// + /// if non-null, the ddl will be written to this TextWriter. + /// if the ddl should be executed against the Database. + /// Optional explicit connection. Required for multi-tenancy. + /// Must be an opened connection. The method doesn't close the connection. + /// + /// This is a convenience method that calls and sets + /// the justDrop parameter to false. + /// + public void Create(TextWriter exportOutput, bool execute, DbConnection connection = null) + { + InitConnectionAndExecute(null, execute, false, connection, exportOutput); } + //TODO 6.0: Remove (replaced by method with optional connection parameter) /// /// Run the drop schema script /// @@ -146,9 +201,26 @@ public void Create(TextWriter exportOutput, bool execute) /// public void Drop(bool useStdOut, bool execute) { - Execute(useStdOut, execute, true); + Drop(useStdOut, execute, null); + } + + /// + /// Run the drop schema script + /// + /// if the ddl should be outputted in the Console. + /// if the ddl should be executed against the Database. + /// Optional explicit connection. Required for multi-tenancy. + /// Must be an opened connection. The method doesn't close the connection. + /// + /// This is a convenience method that calls and sets + /// the justDrop parameter to true. + /// + public void Drop(bool useStdOut, bool execute, DbConnection connection = null) + { + InitConnectionAndExecute(GetAction(useStdOut), execute, true, connection, null); } + //TODO 6.0: Remove (replaced by method with optional connection parameter) /// /// Run the drop schema script /// @@ -160,7 +232,23 @@ public void Drop(bool useStdOut, bool execute) /// public void Drop(TextWriter exportOutput, bool execute) { - Execute(null, execute, true, exportOutput); + Drop(exportOutput, execute, null); + } + + /// + /// Run the drop schema script + /// + /// if non-null, the ddl will be written to this TextWriter. + /// if the ddl should be executed against the Database. + /// Optional explicit connection. Required for multi-tenancy. + /// Must be an opened connection. The method doesn't close the connection. + /// + /// This is a convenience method that calls and sets + /// the justDrop parameter to true. + /// + public void Drop(TextWriter exportOutput, bool execute, DbConnection connection = null) + { + InitConnectionAndExecute(null, execute, true, connection, exportOutput); } private void ExecuteInitialized(Action scriptAction, bool execute, bool throwOnError, TextWriter exportOutput, @@ -237,14 +325,7 @@ private void ExecuteSql(DbCommand cmd, string sql) public void Execute(bool useStdOut, bool execute, bool justDrop, DbConnection connection, TextWriter exportOutput) { - if (useStdOut) - { - Execute(Console.WriteLine, execute, justDrop, connection, exportOutput); - } - else - { - Execute(null, execute, justDrop, connection, exportOutput); - } + Execute(GetAction(useStdOut), execute, justDrop, connection, exportOutput); } public void Execute(Action scriptAction, bool execute, bool justDrop, DbConnection connection, @@ -315,14 +396,7 @@ public void Execute(Action scriptAction, bool execute, bool justDrop, Db /// public void Execute(bool useStdOut, bool execute, bool justDrop) { - if (useStdOut) - { - Execute(Console.WriteLine, execute, justDrop); - } - else - { - Execute(null, execute, justDrop); - } + InitConnectionAndExecute(GetAction(useStdOut), execute, justDrop, null, null); } public void Execute(Action scriptAction, bool execute, bool justDrop) @@ -331,9 +405,13 @@ public void Execute(Action scriptAction, bool execute, bool justDrop) } public void Execute(Action scriptAction, bool execute, bool justDrop, TextWriter exportOutput) + { + InitConnectionAndExecute(scriptAction, execute, justDrop, null, exportOutput); + } + + private void InitConnectionAndExecute(Action scriptAction, bool execute, bool justDrop, DbConnection connection, TextWriter exportOutput) { Initialize(); - DbConnection connection = null; TextWriter fileOutput = exportOutput; IConnectionProvider connectionProvider = null; @@ -344,8 +422,13 @@ public void Execute(Action scriptAction, bool execute, bool justDrop, Te fileOutput = new StreamWriter(outputFile); } - if (execute) + if (execute && connection == null) { + if (_requireTenantConnection) + { + throw new ArgumentException("When Database multi-tenancy is enabled you need to provide explicit connection. Please use overload with connection parameter."); + } + var props = new Dictionary(); foreach (var de in dialect.DefaultProperties) { @@ -378,12 +461,17 @@ public void Execute(Action scriptAction, bool execute, bool justDrop, Te } finally { - if (connection != null) + if (connectionProvider != null) { connectionProvider.CloseConnection(connection); connectionProvider.Dispose(); } } } + + private static Action GetAction(bool useStdOut) + { + return useStdOut ? Console.WriteLine : (Action) null; + } } } From 07700c5aa2b53040f5990e251da9bb1d9d9ec0e6 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Tue, 9 Apr 2019 15:46:42 +0300 Subject: [PATCH 06/27] Fix async ambiguous methods --- src/NHibernate/Async/Tool/hbm2ddl/SchemaExport.cs | 15 ++++++++++----- src/NHibernate/Tool/hbm2ddl/SchemaExport.cs | 15 ++++++++++----- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/NHibernate/Async/Tool/hbm2ddl/SchemaExport.cs b/src/NHibernate/Async/Tool/hbm2ddl/SchemaExport.cs index 74a6f5595b7..1d3e63bb45a 100644 --- a/src/NHibernate/Async/Tool/hbm2ddl/SchemaExport.cs +++ b/src/NHibernate/Async/Tool/hbm2ddl/SchemaExport.cs @@ -71,6 +71,7 @@ public partial class SchemaExport return CreateAsync(useStdOut, execute, null, cancellationToken); } + //TODO 6.0: Make connection parameter optional: DbConnection connection = null /// /// Run the schema creation script /// @@ -83,7 +84,7 @@ public partial class SchemaExport /// This is a convenience method that calls and sets /// the justDrop parameter to false. /// - public Task CreateAsync(bool useStdOut, bool execute, DbConnection connection = null, CancellationToken cancellationToken = default(CancellationToken)) + public Task CreateAsync(bool useStdOut, bool execute, DbConnection connection, CancellationToken cancellationToken = default(CancellationToken)) { if (cancellationToken.IsCancellationRequested) { @@ -119,6 +120,7 @@ public partial class SchemaExport return CreateAsync(scriptAction, execute, null, cancellationToken); } + //TODO 6.0: Make connection parameter optional: DbConnection connection = null /// /// Run the schema creation script /// @@ -131,7 +133,7 @@ public partial class SchemaExport /// This is a convenience method that calls and sets /// the justDrop parameter to false. /// - public Task CreateAsync(Action scriptAction, bool execute, DbConnection connection = null, CancellationToken cancellationToken = default(CancellationToken)) + public Task CreateAsync(Action scriptAction, bool execute, DbConnection connection, CancellationToken cancellationToken = default(CancellationToken)) { if (cancellationToken.IsCancellationRequested) { @@ -160,6 +162,7 @@ public partial class SchemaExport return CreateAsync(exportOutput, execute, null, cancellationToken); } + //TODO 6.0: Make connection parameter optional: DbConnection connection = null /// /// Run the schema creation script /// @@ -172,7 +175,7 @@ public partial class SchemaExport /// This is a convenience method that calls and sets /// the justDrop parameter to false. /// - public Task CreateAsync(TextWriter exportOutput, bool execute, DbConnection connection = null, CancellationToken cancellationToken = default(CancellationToken)) + public Task CreateAsync(TextWriter exportOutput, bool execute, DbConnection connection, CancellationToken cancellationToken = default(CancellationToken)) { if (cancellationToken.IsCancellationRequested) { @@ -201,6 +204,7 @@ public partial class SchemaExport return DropAsync(useStdOut, execute, null, cancellationToken); } + //TODO 6.0: Make connection parameter optional: DbConnection connection = null /// /// Run the drop schema script /// @@ -213,7 +217,7 @@ public partial class SchemaExport /// This is a convenience method that calls and sets /// the justDrop parameter to true. /// - public Task DropAsync(bool useStdOut, bool execute, DbConnection connection = null, CancellationToken cancellationToken = default(CancellationToken)) + public Task DropAsync(bool useStdOut, bool execute, DbConnection connection, CancellationToken cancellationToken = default(CancellationToken)) { if (cancellationToken.IsCancellationRequested) { @@ -249,6 +253,7 @@ public partial class SchemaExport return DropAsync(exportOutput, execute, null, cancellationToken); } + //TODO 6.0: Make connection parameter optional: DbConnection connection = null /// /// Run the drop schema script /// @@ -261,7 +266,7 @@ public partial class SchemaExport /// This is a convenience method that calls and sets /// the justDrop parameter to true. /// - public Task DropAsync(TextWriter exportOutput, bool execute, DbConnection connection = null, CancellationToken cancellationToken = default(CancellationToken)) + public Task DropAsync(TextWriter exportOutput, bool execute, DbConnection connection, CancellationToken cancellationToken = default(CancellationToken)) { if (cancellationToken.IsCancellationRequested) { diff --git a/src/NHibernate/Tool/hbm2ddl/SchemaExport.cs b/src/NHibernate/Tool/hbm2ddl/SchemaExport.cs index 0a203a272c6..002b2f0a649 100644 --- a/src/NHibernate/Tool/hbm2ddl/SchemaExport.cs +++ b/src/NHibernate/Tool/hbm2ddl/SchemaExport.cs @@ -111,6 +111,7 @@ public void Create(bool useStdOut, bool execute) Create(useStdOut, execute, null); } + //TODO 6.0: Make connection parameter optional: DbConnection connection = null /// /// Run the schema creation script /// @@ -122,7 +123,7 @@ public void Create(bool useStdOut, bool execute) /// This is a convenience method that calls and sets /// the justDrop parameter to false. /// - public void Create(bool useStdOut, bool execute, DbConnection connection = null) + public void Create(bool useStdOut, bool execute, DbConnection connection) { InitConnectionAndExecute(GetAction(useStdOut), execute, false, connection, null); } @@ -142,6 +143,7 @@ public void Create(Action scriptAction, bool execute) Create(scriptAction, execute, null); } + //TODO 6.0: Make connection parameter optional: DbConnection connection = null /// /// Run the schema creation script /// @@ -153,7 +155,7 @@ public void Create(Action scriptAction, bool execute) /// This is a convenience method that calls and sets /// the justDrop parameter to false. /// - public void Create(Action scriptAction, bool execute, DbConnection connection = null) + public void Create(Action scriptAction, bool execute, DbConnection connection) { InitConnectionAndExecute(scriptAction, execute, false, connection, null); } @@ -173,6 +175,7 @@ public void Create(TextWriter exportOutput, bool execute) Create(exportOutput, execute, null); } + //TODO 6.0: Make connection parameter optional: DbConnection connection = null /// /// Run the schema creation script /// @@ -184,7 +187,7 @@ public void Create(TextWriter exportOutput, bool execute) /// This is a convenience method that calls and sets /// the justDrop parameter to false. /// - public void Create(TextWriter exportOutput, bool execute, DbConnection connection = null) + public void Create(TextWriter exportOutput, bool execute, DbConnection connection) { InitConnectionAndExecute(null, execute, false, connection, exportOutput); } @@ -204,6 +207,7 @@ public void Drop(bool useStdOut, bool execute) Drop(useStdOut, execute, null); } + //TODO 6.0: Make connection parameter optional: DbConnection connection = null /// /// Run the drop schema script /// @@ -215,7 +219,7 @@ public void Drop(bool useStdOut, bool execute) /// This is a convenience method that calls and sets /// the justDrop parameter to true. /// - public void Drop(bool useStdOut, bool execute, DbConnection connection = null) + public void Drop(bool useStdOut, bool execute, DbConnection connection) { InitConnectionAndExecute(GetAction(useStdOut), execute, true, connection, null); } @@ -235,6 +239,7 @@ public void Drop(TextWriter exportOutput, bool execute) Drop(exportOutput, execute, null); } + //TODO 6.0: Make connection parameter optional: DbConnection connection = null /// /// Run the drop schema script /// @@ -246,7 +251,7 @@ public void Drop(TextWriter exportOutput, bool execute) /// This is a convenience method that calls and sets /// the justDrop parameter to true. /// - public void Drop(TextWriter exportOutput, bool execute, DbConnection connection = null) + public void Drop(TextWriter exportOutput, bool execute, DbConnection connection) { InitConnectionAndExecute(null, execute, true, connection, exportOutput); } From 6203c5725abc2bc6f87cb12c1b5cbb98de65c102 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Tue, 9 Apr 2019 17:10:20 +0300 Subject: [PATCH 07/27] Commented --- src/NHibernate/Connection/IConnectionAccess.cs | 1 + .../AbstractMultiTenantConnectionProvider.cs | 6 ++++++ .../MultiTenancy/IMultiTenantConnectionProvider.cs | 7 +++++++ src/NHibernate/MultiTenancy/MultiTenancyStrategy.cs | 4 ++++ src/NHibernate/MultiTenancy/TenantConfiguration.cs | 11 +++++++++++ 5 files changed, 29 insertions(+) diff --git a/src/NHibernate/Connection/IConnectionAccess.cs b/src/NHibernate/Connection/IConnectionAccess.cs index 8e80685830a..58b2b361a06 100644 --- a/src/NHibernate/Connection/IConnectionAccess.cs +++ b/src/NHibernate/Connection/IConnectionAccess.cs @@ -6,6 +6,7 @@ namespace NHibernate.Connection /// /// Provides centralized access to connections. Centralized to hide the complexity of accounting for contextual /// (multi-tenant) versus non-contextual access. + /// Implementation must be serializable /// public partial interface IConnectionAccess { diff --git a/src/NHibernate/MultiTenancy/AbstractMultiTenantConnectionProvider.cs b/src/NHibernate/MultiTenancy/AbstractMultiTenantConnectionProvider.cs index 96fd40714e8..7dfced1671e 100644 --- a/src/NHibernate/MultiTenancy/AbstractMultiTenantConnectionProvider.cs +++ b/src/NHibernate/MultiTenancy/AbstractMultiTenantConnectionProvider.cs @@ -12,9 +12,15 @@ namespace NHibernate.MultiTenancy [Serializable] public abstract partial class AbstractMultiTenantConnectionProvider : IMultiTenantConnectionProvider { + /// + /// Tenant connection string + /// protected abstract string TenantConnectionString { get; } + + /// public abstract string TenantIdentifier { get; } + /// public IConnectionAccess GetConnectionAccess() { //TODO 6.0: Remove check diff --git a/src/NHibernate/MultiTenancy/IMultiTenantConnectionProvider.cs b/src/NHibernate/MultiTenancy/IMultiTenantConnectionProvider.cs index 61de06f3886..b8ef18f1478 100644 --- a/src/NHibernate/MultiTenancy/IMultiTenantConnectionProvider.cs +++ b/src/NHibernate/MultiTenancy/IMultiTenantConnectionProvider.cs @@ -8,8 +8,15 @@ namespace NHibernate.MultiTenancy /// public interface IMultiTenantConnectionProvider { + /// + /// Tenant identifier must uniquely identify tenant (is used for data separation in cache) + /// Note: Among other things this value is used for data separation between tenants in cache so not unique value will leak data to other tenants + /// string TenantIdentifier { get; } + /// + /// Tenant connection access + /// IConnectionAccess GetConnectionAccess(); } } diff --git a/src/NHibernate/MultiTenancy/MultiTenancyStrategy.cs b/src/NHibernate/MultiTenancy/MultiTenancyStrategy.cs index ce1b2477424..3080862a27b 100644 --- a/src/NHibernate/MultiTenancy/MultiTenancyStrategy.cs +++ b/src/NHibernate/MultiTenancy/MultiTenancyStrategy.cs @@ -1,5 +1,9 @@ namespace NHibernate.MultiTenancy { + /// + /// Strategy for multi-tenancy + /// + /// public enum MultiTenancyStrategy { /// diff --git a/src/NHibernate/MultiTenancy/TenantConfiguration.cs b/src/NHibernate/MultiTenancy/TenantConfiguration.cs index 6591976bc92..719afae22b2 100644 --- a/src/NHibernate/MultiTenancy/TenantConfiguration.cs +++ b/src/NHibernate/MultiTenancy/TenantConfiguration.cs @@ -7,9 +7,20 @@ namespace NHibernate.MultiTenancy /// public class TenantConfiguration { + /// + /// Tenant identifier must uniquely identify tenant + /// Note: Among other things this value is used for data separation between tenants in cache so not unique value will leak data to other tenants + /// public string TenantIdentifier { get; set; } + + /// + /// Tenant connection access. Required for multi-tenancy strategy. + /// public IConnectionAccess ConnectionAccess { get; set; } + /// + /// Creates tenant configuration for multi-tenancy strategy. + /// public TenantConfiguration(IMultiTenantConnectionProvider tenantConnectionProvider) { TenantIdentifier = tenantConnectionProvider.TenantIdentifier; From 37553ddd89882fe205a64d544d891915984de257 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Tue, 9 Apr 2019 17:11:24 +0300 Subject: [PATCH 08/27] Fix firebird tests --- src/NHibernate.Test/TestCase.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/NHibernate.Test/TestCase.cs b/src/NHibernate.Test/TestCase.cs index fb66557e891..f1d04a96640 100644 --- a/src/NHibernate.Test/TestCase.cs +++ b/src/NHibernate.Test/TestCase.cs @@ -298,11 +298,10 @@ protected virtual void CreateSchema() protected virtual void DropSchema() { - using (var optionalConnection = OpenConnectionForSchemaExport()) - DropSchema(OutputDdl, SchemaExport, Sfi, optionalConnection); + DropSchema(OutputDdl, SchemaExport, Sfi, OpenConnectionForSchemaExport); } - public static void DropSchema(bool useStdOut, SchemaExport export, ISessionFactoryImplementor sfi, DbConnection optionalConnection = null) + public static void DropSchema(bool useStdOut, SchemaExport export, ISessionFactoryImplementor sfi, Func getConnection = null) { if (sfi?.ConnectionProvider.Driver is FirebirdClientDriver fbDriver) { @@ -315,7 +314,8 @@ public static void DropSchema(bool useStdOut, SchemaExport export, ISessionFacto fbDriver.ClearPool(null); } - export.Drop(useStdOut, true, optionalConnection); + using(var optionalConnection = getConnection?.Invoke()) + export.Drop(useStdOut, true, optionalConnection); } /// From b68ed76db6a3d470f4d7a9572caa28a4f1c01ae2 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Wed, 10 Apr 2019 12:49:10 +0300 Subject: [PATCH 09/27] Shared and stateless session support; Rename MultiTenant to MultiTenancy for consistency; Added proper session tenant info serialization --- .../DatabaseStrategyNoDbSpecificFixture.cs | 42 +++++-- src/NHibernate.Test/DebugSessionFactory.cs | 14 ++- .../DatabaseStrategyNoDbSpecificFixture.cs | 115 ++++++++++++++++-- .../Async/Engine/ISessionImplementor.cs | 2 +- src/NHibernate/Async/Impl/SessionImpl.cs | 1 + .../Async/Tool/hbm2ddl/SchemaExport.cs | 2 +- src/NHibernate/Cfg/Environment.cs | 2 +- .../DbIntegrationConfigurationProperties.cs | 5 + .../IDbIntegrationConfigurationProperties.cs | 3 + src/NHibernate/Cfg/SettingsFactory.cs | 2 +- src/NHibernate/Engine/ISessionImplementor.cs | 4 +- src/NHibernate/ISessionBuilder.cs | 4 + src/NHibernate/IStatelessSessionBuilder.cs | 21 +++- src/NHibernate/Impl/AbstractSessionImpl.cs | 15 ++- src/NHibernate/Impl/SessionFactoryImpl.cs | 16 ++- src/NHibernate/Impl/SessionImpl.cs | 5 + .../MultiTenancy/MultiTenancyStrategy.cs | 2 +- .../MultiTenancy/TenantConfiguration.cs | 2 + src/NHibernate/Tool/hbm2ddl/SchemaExport.cs | 2 +- 19 files changed, 222 insertions(+), 37 deletions(-) diff --git a/src/NHibernate.Test/Async/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs b/src/NHibernate.Test/Async/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs index c5bdb294070..a64cad5d4e7 100644 --- a/src/NHibernate.Test/Async/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs +++ b/src/NHibernate.Test/Async/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs @@ -36,8 +36,8 @@ public class DatabaseStrategyNoDbSpecificFixtureAsync : TestCaseMappingByCode protected override void Configure(Configuration configuration) { - configuration.Properties[Cfg.Environment.MultiTenant] = MultiTenancyStrategy.Database.ToString(); - configuration.Properties[Cfg.Environment.GenerateStatistics] = true.ToString(); + configuration.Properties[Cfg.Environment.MultiTenancy] = MultiTenancyStrategy.Database.ToString(); + configuration.Properties[Cfg.Environment.GenerateStatistics] = "true"; base.Configure(configuration); } @@ -47,6 +47,12 @@ private static void ValidateSqlServerConnectionAppName(ISession s, string tenant Assert.That(builder.ApplicationName, Is.EqualTo(tenantId)); } + private static void ValidateSqlServerConnectionAppName(IStatelessSession s, string tenantId) + { + var builder = new SqlConnectionStringBuilder(s.Connection.ConnectionString); + Assert.That(builder.ApplicationName, Is.EqualTo(tenantId)); + } + [Test] public async Task SecondLevelCacheReusedForSameTenantAsync() { @@ -118,7 +124,7 @@ public async Task QueryCacheSeparationPerTenantAsync() Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(1)); Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(0)); } - + [Test] public async Task TenantSessionIsSerializableAndCanBeReconnectedAsync() { @@ -134,21 +140,38 @@ public async Task TenantSessionIsSerializableAndCanBeReconnectedAsync() using (deserializedSession) { deserializedSession.Reconnect(); + + //Expect session cache hit var entity = await (deserializedSession.GetAsync(_id)); if (IsSqlServerDialect) ValidateSqlServerConnectionAppName(deserializedSession, "tenant1"); + deserializedSession.Clear(); + + //Expect second level cache hit + await (deserializedSession.GetAsync(_id)); + Assert.That(GetTenantId(deserializedSession), Is.EqualTo("tenant1")); } Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0)); - Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(0)); + Assert.That(Sfi.Statistics.SecondLevelCacheHitCount, Is.EqualTo(1)); + } + + private static string GetTenantId(ISession deserializedSession) + { + return deserializedSession.GetSessionImplementation().GetTenantIdentifier(); + } + + private static string GetTenantId(IStatelessSession deserializedSession) + { + return deserializedSession.GetSessionImplementation().GetTenantIdentifier(); } - private ISession SpoofSerialization(ISession session) + private T SpoofSerialization(T session) { var formatter = new BinaryFormatter { #if !NETFX - SurrogateSelector = new SerializationHelper.SurrogateSelector() + SurrogateSelector = new SerializationHelper.SurrogateSelector() #endif }; MemoryStream stream = new MemoryStream(); @@ -156,13 +179,18 @@ private ISession SpoofSerialization(ISession session) stream.Position = 0; - return (ISession) formatter.Deserialize(stream); + return (T) formatter.Deserialize(stream); } private ISession OpenTenantSession(string tenantId) { return Sfi.WithOptions().TenantConfiguration(GetTenantConfig(tenantId)).OpenSession(); } + + private IStatelessSession OpenTenantStatelessSession(string tenantId) + { + return Sfi.WithStatelessOptions().TenantConfiguration(GetTenantConfig(tenantId)).OpenStatelessSession(); + } private TenantConfiguration GetTenantConfig(string tenantId) { diff --git a/src/NHibernate.Test/DebugSessionFactory.cs b/src/NHibernate.Test/DebugSessionFactory.cs index 122184ba5f1..037cb56b795 100644 --- a/src/NHibernate.Test/DebugSessionFactory.cs +++ b/src/NHibernate.Test/DebugSessionFactory.cs @@ -401,7 +401,9 @@ public static ISessionCreationOptions GetCreationOptions(IStatelessSessionBuilde (ISessionCreationOptions)sessionBuilder; } - internal class SessionBuilder : ISessionBuilder, ISessionCreationOptionsWithMultiTenancy + internal class SessionBuilder : ISessionBuilder, + //TODO 6.0: Remove interface with implementation (will be replaced TenantConfiguration ISessionBuilder method) + ISessionCreationOptionsWithMultiTenancy { private readonly ISessionBuilder _actualBuilder; private readonly DebugSessionFactory _debugFactory; @@ -474,7 +476,9 @@ TenantConfiguration ISessionCreationOptionsWithMultiTenancy.TenantConfiguration } } - internal class StatelessSessionBuilder : IStatelessSessionBuilder + internal class StatelessSessionBuilder : IStatelessSessionBuilder, + //TODO 6.0: Remove interface with implementation (will be replaced TenantConfiguration IStatelessSessionBuilder method) + ISessionCreationOptionsWithMultiTenancy { private readonly IStatelessSessionBuilder _actualBuilder; private readonly DebugSessionFactory _debugFactory; @@ -508,6 +512,12 @@ IStatelessSessionBuilder IStatelessSessionBuilder.AutoJoinTransaction(bool autoJ return this; } + TenantConfiguration ISessionCreationOptionsWithMultiTenancy.TenantConfiguration + { + get => (_actualBuilder as ISessionCreationOptionsWithMultiTenancy)?.TenantConfiguration; + set => _actualBuilder.TenantConfiguration(value); + } + #endregion } } diff --git a/src/NHibernate.Test/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs b/src/NHibernate.Test/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs index e28f1cadea2..c22516dcd95 100644 --- a/src/NHibernate.Test/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs +++ b/src/NHibernate.Test/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs @@ -25,8 +25,8 @@ public class DatabaseStrategyNoDbSpecificFixture : TestCaseMappingByCode protected override void Configure(Configuration configuration) { - configuration.Properties[Cfg.Environment.MultiTenant] = MultiTenancyStrategy.Database.ToString(); - configuration.Properties[Cfg.Environment.GenerateStatistics] = true.ToString(); + configuration.Properties[Cfg.Environment.MultiTenancy] = MultiTenancyStrategy.Database.ToString(); + configuration.Properties[Cfg.Environment.GenerateStatistics] = "true"; base.Configure(configuration); } @@ -37,11 +37,11 @@ public void ShouldThrowWithNoTenantIdentifier() Assert.That(() => sessionBuilder.OpenSession(), Throws.ArgumentException); } - + [Test] public void ShouldThrowWithNoConnectionAccess() { - var sessionBuilder = Sfi.WithOptions().TenantConfiguration(new TenantConfiguration(new MockConnectionProvider("tenant1", null))); + var sessionBuilder = Sfi.WithOptions().TenantConfiguration(new TenantConfiguration(new MockConnectionProvider("tenant1", null))); Assert.That(() => sessionBuilder.OpenSession(), Throws.ArgumentException); } @@ -58,6 +58,75 @@ public void DifferentConnectionStringForDifferentTenants() Assert.That(session1.Connection.ConnectionString, Is.Not.EqualTo(session2.Connection.ConnectionString)); ValidateSqlServerConnectionAppName(session1, "tenant1"); ValidateSqlServerConnectionAppName(session2, "tenant2"); + Assert.That(GetTenantId(session1), Is.EqualTo("tenant1")); + Assert.That(GetTenantId(session2), Is.EqualTo("tenant2")); + } + } + + [Test] + public void StatelessSessionShouldThrowWithNoTenantIdentifier() + { + var sessionBuilder = Sfi.WithStatelessOptions().TenantConfiguration(new TenantConfiguration(new MockConnectionProvider(null, null))); + + Assert.That(() => sessionBuilder.OpenStatelessSession(), Throws.ArgumentException); + } + + [Test] + public void StatelessSessionShouldThrowWithNoConnectionAccess() + { + var sessionBuilder = Sfi.WithStatelessOptions().TenantConfiguration(new TenantConfiguration(new MockConnectionProvider("tenant1", null))); + + Assert.That(() => sessionBuilder.OpenStatelessSession(), Throws.ArgumentException); + } + + [Test] + public void StatelessSessionDifferentConnectionStringForDifferentTenants() + { + if (!IsSqlServerDialect) + Assert.Ignore("MSSqlServer specific test"); + + using(var session1 = OpenTenantStatelessSession("tenant1")) + using (var session2 = OpenTenantStatelessSession("tenant2")) + { + Assert.That(session1.Connection.ConnectionString, Is.Not.EqualTo(session2.Connection.ConnectionString)); + ValidateSqlServerConnectionAppName(session1, "tenant1"); + ValidateSqlServerConnectionAppName(session2, "tenant2"); + Assert.That(GetTenantId(session1), Is.EqualTo("tenant1")); + Assert.That(GetTenantId(session2), Is.EqualTo("tenant2")); + } + } + + [Test] + public void SharedSessionSameConnectionString() + { + using (var session1 = OpenTenantSession("tenant1")) + using (var session2 = session1.SessionWithOptions().OpenSession()) + { + Assert.That(session1.Connection, Is.Not.EqualTo(session2.Connection)); + Assert.That(session1.Connection.ConnectionString, Is.EqualTo(session2.Connection.ConnectionString)); + Assert.That(session2.GetSessionImplementation().GetTenantIdentifier(), Is.EqualTo("tenant1")); + } + } + + [Test] + public void SharedSessionSameConnection() + { + using (var session1 = OpenTenantSession("tenant1")) + using (var session2 = session1.SessionWithOptions().Connection().OpenSession()) + { + Assert.That(session1.Connection, Is.EqualTo(session2.Connection)); + Assert.That(GetTenantId(session2), Is.EqualTo("tenant1")); + } + } + + [Test] + public void SharedStatelessSessionSameConnectionString() + { + using (var session1 = OpenTenantSession("tenant1")) + using (var session2 = session1.StatelessSessionWithOptions().OpenStatelessSession()) + { + Assert.That(session1.Connection.ConnectionString, Is.EqualTo(session2.Connection.ConnectionString)); + Assert.That(GetTenantId(session2), Is.EqualTo("tenant1")); } } @@ -67,6 +136,12 @@ private static void ValidateSqlServerConnectionAppName(ISession s, string tenant Assert.That(builder.ApplicationName, Is.EqualTo(tenantId)); } + private static void ValidateSqlServerConnectionAppName(IStatelessSession s, string tenantId) + { + var builder = new SqlConnectionStringBuilder(s.Connection.ConnectionString); + Assert.That(builder.ApplicationName, Is.EqualTo(tenantId)); + } + [Test] public void SecondLevelCacheReusedForSameTenant() { @@ -138,7 +213,7 @@ public void QueryCacheSeparationPerTenant() Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(1)); Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(0)); } - + [Test] public void TenantSessionIsSerializableAndCanBeReconnected() { @@ -154,21 +229,38 @@ public void TenantSessionIsSerializableAndCanBeReconnected() using (deserializedSession) { deserializedSession.Reconnect(); + + //Expect session cache hit var entity = deserializedSession.Get(_id); if (IsSqlServerDialect) ValidateSqlServerConnectionAppName(deserializedSession, "tenant1"); + deserializedSession.Clear(); + + //Expect second level cache hit + deserializedSession.Get(_id); + Assert.That(GetTenantId(deserializedSession), Is.EqualTo("tenant1")); } Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0)); - Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(0)); + Assert.That(Sfi.Statistics.SecondLevelCacheHitCount, Is.EqualTo(1)); } - private ISession SpoofSerialization(ISession session) + private static string GetTenantId(ISession deserializedSession) + { + return deserializedSession.GetSessionImplementation().GetTenantIdentifier(); + } + + private static string GetTenantId(IStatelessSession deserializedSession) + { + return deserializedSession.GetSessionImplementation().GetTenantIdentifier(); + } + + private T SpoofSerialization(T session) { var formatter = new BinaryFormatter { #if !NETFX - SurrogateSelector = new SerializationHelper.SurrogateSelector() + SurrogateSelector = new SerializationHelper.SurrogateSelector() #endif }; MemoryStream stream = new MemoryStream(); @@ -176,13 +268,18 @@ private ISession SpoofSerialization(ISession session) stream.Position = 0; - return (ISession) formatter.Deserialize(stream); + return (T) formatter.Deserialize(stream); } private ISession OpenTenantSession(string tenantId) { return Sfi.WithOptions().TenantConfiguration(GetTenantConfig(tenantId)).OpenSession(); } + + private IStatelessSession OpenTenantStatelessSession(string tenantId) + { + return Sfi.WithStatelessOptions().TenantConfiguration(GetTenantConfig(tenantId)).OpenStatelessSession(); + } private TenantConfiguration GetTenantConfig(string tenantId) { diff --git a/src/NHibernate/Async/Engine/ISessionImplementor.cs b/src/NHibernate/Async/Engine/ISessionImplementor.cs index d5108c3e8f8..0036bb9918e 100644 --- a/src/NHibernate/Async/Engine/ISessionImplementor.cs +++ b/src/NHibernate/Async/Engine/ISessionImplementor.cs @@ -30,7 +30,7 @@ namespace NHibernate.Engine { using System.Threading.Tasks; using System.Threading; - internal static partial class SessionImplementorExtensions + public static partial class SessionImplementorExtensions { internal static async Task AutoFlushIfRequiredAsync(this ISessionImplementor implementor, ISet querySpaces, CancellationToken cancellationToken) diff --git a/src/NHibernate/Async/Impl/SessionImpl.cs b/src/NHibernate/Async/Impl/SessionImpl.cs index 03d89beaa52..a8a01f90dae 100644 --- a/src/NHibernate/Async/Impl/SessionImpl.cs +++ b/src/NHibernate/Async/Impl/SessionImpl.cs @@ -26,6 +26,7 @@ using NHibernate.Intercept; using NHibernate.Loader.Criteria; using NHibernate.Loader.Custom; +using NHibernate.MultiTenancy; using NHibernate.Persister.Collection; using NHibernate.Persister.Entity; using NHibernate.Proxy; diff --git a/src/NHibernate/Async/Tool/hbm2ddl/SchemaExport.cs b/src/NHibernate/Async/Tool/hbm2ddl/SchemaExport.cs index 1d3e63bb45a..6c44940aefa 100644 --- a/src/NHibernate/Async/Tool/hbm2ddl/SchemaExport.cs +++ b/src/NHibernate/Async/Tool/hbm2ddl/SchemaExport.cs @@ -47,7 +47,7 @@ public partial class SchemaExport dropSQL = cfg.GenerateDropSchemaScript(dialect); createSQL = cfg.GenerateSchemaCreationScript(dialect); formatter = (PropertiesHelper.GetBoolean(Environment.FormatSql, configProperties, true) ? FormatStyle.Ddl : FormatStyle.None).Formatter; - _requireTenantConnection = PropertiesHelper.GetEnum(Environment.MultiTenant, configProperties, MultiTenancyStrategy.None) == MultiTenancyStrategy.Database; + _requireTenantConnection = PropertiesHelper.GetEnum(Environment.MultiTenancy, configProperties, MultiTenancyStrategy.None) == MultiTenancyStrategy.Database; wasInitialized = true; } diff --git a/src/NHibernate/Cfg/Environment.cs b/src/NHibernate/Cfg/Environment.cs index 74f47eeb2a1..f9df5d115a6 100644 --- a/src/NHibernate/Cfg/Environment.cs +++ b/src/NHibernate/Cfg/Environment.cs @@ -389,7 +389,7 @@ public static string Version /// /// Strategy for multi-tenancy. /// - public const string MultiTenant = "multiTenancy"; + public const string MultiTenancy = "multiTenancy"; private static IBytecodeProvider BytecodeProviderInstance; private static bool EnableReflectionOptimizer; diff --git a/src/NHibernate/Cfg/Loquacious/DbIntegrationConfigurationProperties.cs b/src/NHibernate/Cfg/Loquacious/DbIntegrationConfigurationProperties.cs index 1901e92b3ce..355f12f7b0f 100644 --- a/src/NHibernate/Cfg/Loquacious/DbIntegrationConfigurationProperties.cs +++ b/src/NHibernate/Cfg/Loquacious/DbIntegrationConfigurationProperties.cs @@ -153,6 +153,11 @@ public void PreTransformerRegistrar() where TRegistrar : IExpression configuration.SetProperty(Environment.PreTransformerRegistrar, typeof(TRegistrar).AssemblyQualifiedName); } + public MultiTenancy.MultiTenancyStrategy MultiTenancy + { + set { configuration.SetProperty(Environment.MultiTenancy, value.ToString()); } + } + #endregion } } diff --git a/src/NHibernate/Cfg/Loquacious/IDbIntegrationConfigurationProperties.cs b/src/NHibernate/Cfg/Loquacious/IDbIntegrationConfigurationProperties.cs index a1c1c546bb4..a84795a4a0c 100644 --- a/src/NHibernate/Cfg/Loquacious/IDbIntegrationConfigurationProperties.cs +++ b/src/NHibernate/Cfg/Loquacious/IDbIntegrationConfigurationProperties.cs @@ -44,5 +44,8 @@ public interface IDbIntegrationConfigurationProperties SchemaAutoAction SchemaAction { set; } void QueryModelRewriterFactory() where TFactory : IQueryModelRewriterFactory; + + //TODO 6.0: Uncomment. Or even better consider dropping all configuration interfaces and use classes directly + //MultiTenancy.MultiTenancyStrategy MultiTenancy { set; } } } diff --git a/src/NHibernate/Cfg/SettingsFactory.cs b/src/NHibernate/Cfg/SettingsFactory.cs index 074fc141aa7..7b5cb5d9372 100644 --- a/src/NHibernate/Cfg/SettingsFactory.cs +++ b/src/NHibernate/Cfg/SettingsFactory.cs @@ -326,7 +326,7 @@ public Settings BuildSettings(IDictionary properties) log.Debug("Track session id: " + EnabledDisabled(trackSessionId)); settings.TrackSessionId = trackSessionId; - var multiTenancyStrategy = PropertiesHelper.GetEnum(Environment.MultiTenant, properties, MultiTenancyStrategy.None); + var multiTenancyStrategy = PropertiesHelper.GetEnum(Environment.MultiTenancy, properties, MultiTenancyStrategy.None); if(multiTenancyStrategy != MultiTenancyStrategy.None) log.Debug("multi-tenancy strategy : " + multiTenancyStrategy); settings.MultiTenancyStrategy = multiTenancyStrategy; diff --git a/src/NHibernate/Engine/ISessionImplementor.cs b/src/NHibernate/Engine/ISessionImplementor.cs index 3bbaa1f147c..2df29ee3e46 100644 --- a/src/NHibernate/Engine/ISessionImplementor.cs +++ b/src/NHibernate/Engine/ISessionImplementor.cs @@ -19,14 +19,14 @@ namespace NHibernate.Engine { // 6.0 TODO: Convert to interface methods, excepted SwitchCacheMode - internal static partial class SessionImplementorExtensions + public static partial class SessionImplementorExtensions { //6.0 TODO: Expose as TenantIdentifier property /// /// Obtain the tenant identifier associated with this session. /// /// The tenant identifier associated with this session or null - internal static string GetTenantIdentifier(this ISessionImplementor session) + public static string GetTenantIdentifier(this ISessionImplementor session) { if (session is AbstractSessionImpl sessionImpl) { diff --git a/src/NHibernate/ISessionBuilder.cs b/src/NHibernate/ISessionBuilder.cs index 08135d1cc87..080e5f0c070 100644 --- a/src/NHibernate/ISessionBuilder.cs +++ b/src/NHibernate/ISessionBuilder.cs @@ -9,6 +9,10 @@ namespace NHibernate //TODO 6.0: Merge into ISessionBuilder public static class SessionBuilderExtensions { + /// + /// Provides tenant configuration required for multi-tenancy + /// + /// public static T TenantConfiguration(this T builder, TenantConfiguration tenantConfig) where T: ISessionBuilder { ReflectHelper.CastOrThrow(builder, "multi tenancy").TenantConfiguration = tenantConfig; diff --git a/src/NHibernate/IStatelessSessionBuilder.cs b/src/NHibernate/IStatelessSessionBuilder.cs index 77641ad9e4e..9356120e3b4 100644 --- a/src/NHibernate/IStatelessSessionBuilder.cs +++ b/src/NHibernate/IStatelessSessionBuilder.cs @@ -1,8 +1,25 @@ using System.Data.Common; using NHibernate.Connection; +using NHibernate.Impl; +using NHibernate.MultiTenancy; +using NHibernate.Util; namespace NHibernate { + //TODO 6.0: Merge into IStatelessSessionBuilder + public static class StatelessSessionBuilderExtensions + { + /// + /// Provides tenant configuration required for multi-tenancy + /// + /// + public static T TenantConfiguration(this T builder, TenantConfiguration tenantConfig) where T: IStatelessSessionBuilder + { + ReflectHelper.CastOrThrow(builder, "multi tenancy").TenantConfiguration = tenantConfig; + return builder; + } + } + // NH different implementation: will not try to support covariant return type for specializations // until it is needed. /// @@ -38,7 +55,5 @@ public interface IStatelessSessionBuilder /// enlisted in ambient transaction. /// , for method chaining. IStatelessSessionBuilder AutoJoinTransaction(bool autoJoinTransaction); - - // NH remark: seems a bit overkill for now. On Hibernate side, they have at least another option: the tenant. } -} \ No newline at end of file +} diff --git a/src/NHibernate/Impl/AbstractSessionImpl.cs b/src/NHibernate/Impl/AbstractSessionImpl.cs index 61308658ffd..029a1678ee9 100644 --- a/src/NHibernate/Impl/AbstractSessionImpl.cs +++ b/src/NHibernate/Impl/AbstractSessionImpl.cs @@ -89,9 +89,7 @@ protected internal AbstractSessionImpl(ISessionFactoryImplementor factory, ISess _flushMode = options.InitialSessionFlushMode; Interceptor = options.SessionInterceptor ?? EmptyInterceptor.Instance; - TenantConfiguration tenantConfiguration = ValidateTenantConfiguration(factory, options); - - TenantIdentifier = tenantConfiguration?.TenantIdentifier; + _tenantConfiguration = ValidateTenantConfiguration(factory, options); if (options is ISharedSessionCreationOptions sharedOptions && sharedOptions.IsTransactionCoordinatorShared) { @@ -110,7 +108,7 @@ protected internal AbstractSessionImpl(ISessionFactoryImplementor factory, ISess options.UserSuppliedConnection, options.SessionConnectionReleaseMode, Interceptor, - tenantConfiguration?.ConnectionAccess ?? new NonContextualConnectionAccess(_factory), + _tenantConfiguration?.ConnectionAccess ?? new NonContextualConnectionAccess(_factory), options.ShouldAutoJoinTransaction); } } @@ -415,6 +413,7 @@ public IDisposable BeginContext() } private ProcessHelper _processHelper = new ProcessHelper(); + private TenantConfiguration _tenantConfiguration; [Serializable] private sealed class ProcessHelper : IDisposable @@ -492,7 +491,13 @@ protected bool IsAlreadyDisposed /// public virtual bool TransactionInProgress => ConnectionManager.IsInActiveTransaction; - public string TenantIdentifier { get; } + protected internal TenantConfiguration TenantConfiguration + { + get => _tenantConfiguration; + protected set => _tenantConfiguration = value; + } + + public string TenantIdentifier => _tenantConfiguration?.TenantIdentifier; #endregion diff --git a/src/NHibernate/Impl/SessionFactoryImpl.cs b/src/NHibernate/Impl/SessionFactoryImpl.cs index bb109719107..d71be14ac4c 100644 --- a/src/NHibernate/Impl/SessionFactoryImpl.cs +++ b/src/NHibernate/Impl/SessionFactoryImpl.cs @@ -1539,9 +1539,12 @@ public virtual T FlushMode(FlushMode flushMode) return _this; } - public TenantConfiguration TenantConfiguration { get; + public TenantConfiguration TenantConfiguration + { + get; //TODO 6.0: Make protected - set; } + set; + } } // NH specific: implementing return type covariance with interface is a mess in .Net. @@ -1553,7 +1556,7 @@ public StatelessSessionBuilderImpl(SessionFactoryImpl sessionFactory) : base(ses } } - internal class StatelessSessionBuilderImpl : IStatelessSessionBuilder, ISessionCreationOptions where T : IStatelessSessionBuilder + internal class StatelessSessionBuilderImpl : IStatelessSessionBuilder, ISessionCreationOptionsWithMultiTenancy, ISessionCreationOptions where T : IStatelessSessionBuilder { // NH specific: implementing return type covariance with interface is a mess in .Net. private T _this; @@ -1594,6 +1597,13 @@ public IStatelessSessionBuilder AutoJoinTransaction(bool autoJoinTransaction) public IInterceptor SessionInterceptor => EmptyInterceptor.Instance; public ConnectionReleaseMode SessionConnectionReleaseMode => ConnectionReleaseMode.AfterTransaction; + + public TenantConfiguration TenantConfiguration + { + get; + //TODO 6.0: Make protected + set; + } } } } diff --git a/src/NHibernate/Impl/SessionImpl.cs b/src/NHibernate/Impl/SessionImpl.cs index 341a8df46aa..5104aea9f27 100644 --- a/src/NHibernate/Impl/SessionImpl.cs +++ b/src/NHibernate/Impl/SessionImpl.cs @@ -16,6 +16,7 @@ using NHibernate.Intercept; using NHibernate.Loader.Criteria; using NHibernate.Loader.Custom; +using NHibernate.MultiTenancy; using NHibernate.Persister.Collection; using NHibernate.Persister.Entity; using NHibernate.Proxy; @@ -104,6 +105,7 @@ private SessionImpl(SerializationInfo info, StreamingContext context) enabledFilterNames = (List)info.GetValue("enabledFilterNames", typeof(List)); ConnectionManager = (ConnectionManager)info.GetValue("connectionManager", typeof(ConnectionManager)); + TenantConfiguration = info.GetValue(nameof(TenantConfiguration)); } /// @@ -143,6 +145,7 @@ void ISerializable.GetObjectData(SerializationInfo info, StreamingContext contex info.AddValue("enabledFilterNames", enabledFilterNames, typeof(List)); info.AddValue("connectionManager", ConnectionManager, typeof(ConnectionManager)); + info.AddValue(nameof(TenantConfiguration), TenantConfiguration); } #endregion @@ -2412,6 +2415,7 @@ public SharedSessionBuilderImpl(SessionImpl session) : base((SessionFactoryImpl)session.Factory) { _session = session; + TenantConfiguration = session.TenantConfiguration; SetSelf(this); } @@ -2464,6 +2468,7 @@ public SharedStatelessSessionBuilderImpl(SessionImpl session) : base((SessionFactoryImpl)session.Factory) { _session = session; + TenantConfiguration = session.TenantConfiguration; SetSelf(this); } diff --git a/src/NHibernate/MultiTenancy/MultiTenancyStrategy.cs b/src/NHibernate/MultiTenancy/MultiTenancyStrategy.cs index 3080862a27b..98e93c3921f 100644 --- a/src/NHibernate/MultiTenancy/MultiTenancyStrategy.cs +++ b/src/NHibernate/MultiTenancy/MultiTenancyStrategy.cs @@ -2,7 +2,7 @@ namespace NHibernate.MultiTenancy { /// /// Strategy for multi-tenancy - /// + /// /// public enum MultiTenancyStrategy { diff --git a/src/NHibernate/MultiTenancy/TenantConfiguration.cs b/src/NHibernate/MultiTenancy/TenantConfiguration.cs index 719afae22b2..e84d95163cc 100644 --- a/src/NHibernate/MultiTenancy/TenantConfiguration.cs +++ b/src/NHibernate/MultiTenancy/TenantConfiguration.cs @@ -1,3 +1,4 @@ +using System; using NHibernate.Connection; namespace NHibernate.MultiTenancy @@ -5,6 +6,7 @@ namespace NHibernate.MultiTenancy /// /// Tenant specific configuration /// + [Serializable] public class TenantConfiguration { /// diff --git a/src/NHibernate/Tool/hbm2ddl/SchemaExport.cs b/src/NHibernate/Tool/hbm2ddl/SchemaExport.cs index 002b2f0a649..1cdd27614fe 100644 --- a/src/NHibernate/Tool/hbm2ddl/SchemaExport.cs +++ b/src/NHibernate/Tool/hbm2ddl/SchemaExport.cs @@ -70,7 +70,7 @@ private void Initialize() dropSQL = cfg.GenerateDropSchemaScript(dialect); createSQL = cfg.GenerateSchemaCreationScript(dialect); formatter = (PropertiesHelper.GetBoolean(Environment.FormatSql, configProperties, true) ? FormatStyle.Ddl : FormatStyle.None).Formatter; - _requireTenantConnection = PropertiesHelper.GetEnum(Environment.MultiTenant, configProperties, MultiTenancyStrategy.None) == MultiTenancyStrategy.Database; + _requireTenantConnection = PropertiesHelper.GetEnum(Environment.MultiTenancy, configProperties, MultiTenancyStrategy.None) == MultiTenancyStrategy.Database; wasInitialized = true; } From 50175699e37462a8995ecd88748b083a6ab74ff8 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Wed, 10 Apr 2019 13:21:41 +0300 Subject: [PATCH 10/27] Fix CI compiler error --- src/NHibernate/IStatelessSessionBuilder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NHibernate/IStatelessSessionBuilder.cs b/src/NHibernate/IStatelessSessionBuilder.cs index 9356120e3b4..2dc3891a36d 100644 --- a/src/NHibernate/IStatelessSessionBuilder.cs +++ b/src/NHibernate/IStatelessSessionBuilder.cs @@ -13,7 +13,7 @@ public static class StatelessSessionBuilderExtensions /// Provides tenant configuration required for multi-tenancy /// /// - public static T TenantConfiguration(this T builder, TenantConfiguration tenantConfig) where T: IStatelessSessionBuilder + public static IStatelessSessionBuilder TenantConfiguration(this IStatelessSessionBuilder builder, TenantConfiguration tenantConfig) { ReflectHelper.CastOrThrow(builder, "multi tenancy").TenantConfiguration = tenantConfig; return builder; From ba95ae6079666dd4be361b2223d4eaa95aa9c1dd Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Tue, 18 Jun 2019 15:17:35 +0300 Subject: [PATCH 11/27] Undo obsolete change --- .../CfgTest/Loquacious/LambdaConfigurationFixture.cs | 6 ++++-- .../Cfg/Loquacious/IDbIntegrationConfigurationProperties.cs | 3 --- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/NHibernate.Test/CfgTest/Loquacious/LambdaConfigurationFixture.cs b/src/NHibernate.Test/CfgTest/Loquacious/LambdaConfigurationFixture.cs index fcf841757a0..373543bc103 100644 --- a/src/NHibernate.Test/CfgTest/Loquacious/LambdaConfigurationFixture.cs +++ b/src/NHibernate.Test/CfgTest/Loquacious/LambdaConfigurationFixture.cs @@ -3,7 +3,6 @@ using NHibernate.Bytecode; using NHibernate.Cache; using NHibernate.Cfg; -using NHibernate.Cfg.Loquacious; using NHibernate.Dialect; using NHibernate.Driver; using NHibernate.Hql.Ast.ANTLR; @@ -11,6 +10,7 @@ using NHibernate.Type; using NUnit.Framework; using NHibernate.Exceptions; +using NHibernate.MultiTenancy; namespace NHibernate.Test.CfgTest.Loquacious { @@ -64,6 +64,7 @@ public void FullConfiguration() db.HqlToSqlSubstitutions = "true 1, false 0, yes 'Y', no 'N'"; db.SchemaAction = SchemaAutoAction.Validate; db.ThrowOnSchemaUpdate = true; + db.MultiTenancy = MultiTenancyStrategy.Database; }); Assert.That(configure.Properties[Environment.SessionFactoryName], Is.EqualTo("SomeName")); @@ -109,7 +110,8 @@ public void FullConfiguration() Assert.That(configure.Properties[Environment.Hbm2ddlAuto], Is.EqualTo("validate")); Assert.That(configure.Properties[Environment.Hbm2ddlThrowOnUpdate], Is.EqualTo("true")); Assert.That(configure.Properties[Environment.LinqToHqlGeneratorsRegistry], Is.EqualTo(typeof(DefaultLinqToHqlGeneratorsRegistry).AssemblyQualifiedName)); - + Assert.That(configure.Properties[Environment.MultiTenancy], Is.EqualTo(nameof(MultiTenancyStrategy.Database))); + // Keywords import and auto-validation require a valid connection string, disable them before checking // the session factory can be built. configure.SetProperty(Environment.Hbm2ddlKeyWords, "none"); diff --git a/src/NHibernate/Cfg/Loquacious/IDbIntegrationConfigurationProperties.cs b/src/NHibernate/Cfg/Loquacious/IDbIntegrationConfigurationProperties.cs index a84795a4a0c..a1c1c546bb4 100644 --- a/src/NHibernate/Cfg/Loquacious/IDbIntegrationConfigurationProperties.cs +++ b/src/NHibernate/Cfg/Loquacious/IDbIntegrationConfigurationProperties.cs @@ -44,8 +44,5 @@ public interface IDbIntegrationConfigurationProperties SchemaAutoAction SchemaAction { set; } void QueryModelRewriterFactory() where TFactory : IQueryModelRewriterFactory; - - //TODO 6.0: Uncomment. Or even better consider dropping all configuration interfaces and use classes directly - //MultiTenancy.MultiTenancyStrategy MultiTenancy { set; } } } From 9461f9340b64eb1c3e5f15a618c063401ff3d83e Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Tue, 19 May 2020 11:22:11 +0300 Subject: [PATCH 12/27] Fix CodeFactor issues --- .../DatabaseStrategyNoDbSpecificFixture.cs | 1 - .../DatabaseStrategyNoDbSpecificFixture.cs | 48 ------------------- src/NHibernate.Test/MultiTenancy/Entity.cs | 11 +++++ .../MultiTenancy/MockConnectionProvider.cs | 22 +++++++++ .../TestTenantConnectionProvider.cs | 31 ++++++++++++ src/NHibernate/AdoNet/ConnectionManager.cs | 1 - .../Async/Impl/AbstractSessionImpl.cs | 13 ----- .../Impl/NonContextualConnectionAccess.cs | 32 +++++++++++++ src/NHibernate/Cache/CacheKey.cs | 2 +- src/NHibernate/Cache/QueryKey.cs | 2 +- src/NHibernate/Impl/AbstractSessionImpl.cs | 24 ---------- .../Impl/NonContextualConnectionAccess.cs | 30 ++++++++++++ 12 files changed, 128 insertions(+), 89 deletions(-) create mode 100644 src/NHibernate.Test/MultiTenancy/Entity.cs create mode 100644 src/NHibernate.Test/MultiTenancy/MockConnectionProvider.cs create mode 100644 src/NHibernate.Test/MultiTenancy/TestTenantConnectionProvider.cs create mode 100644 src/NHibernate/Async/Impl/NonContextualConnectionAccess.cs create mode 100644 src/NHibernate/Impl/NonContextualConnectionAccess.cs diff --git a/src/NHibernate.Test/Async/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs b/src/NHibernate.Test/Async/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs index a64cad5d4e7..74e65f2e186 100644 --- a/src/NHibernate.Test/Async/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs +++ b/src/NHibernate.Test/Async/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs @@ -16,7 +16,6 @@ using System.Runtime.Serialization.Formatters.Binary; using NHibernate.Cfg; using NHibernate.Cfg.MappingSchema; -using NHibernate.Connection; using NHibernate.Dialect; using NHibernate.Driver; using NHibernate.Engine; diff --git a/src/NHibernate.Test/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs b/src/NHibernate.Test/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs index c22516dcd95..2f288ecd880 100644 --- a/src/NHibernate.Test/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs +++ b/src/NHibernate.Test/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs @@ -6,7 +6,6 @@ using System.Runtime.Serialization.Formatters.Binary; using NHibernate.Cfg; using NHibernate.Cfg.MappingSchema; -using NHibernate.Connection; using NHibernate.Dialect; using NHibernate.Driver; using NHibernate.Engine; @@ -346,51 +345,4 @@ protected override void OnSetUp() #endregion Test Setup } - - [Serializable] - class Entity - { - public virtual Guid Id { get; set; } - public virtual string Name { get; set; } - } - - public class MockConnectionProvider : IMultiTenantConnectionProvider - { - private readonly IConnectionAccess _connectionAccess; - - public MockConnectionProvider(string tenantIdentifier, IConnectionAccess connectionAccess) - { - _connectionAccess = connectionAccess; - TenantIdentifier = tenantIdentifier; - } - - public string TenantIdentifier { get; } - public IConnectionAccess GetConnectionAccess() - { - return _connectionAccess; - } - } - - [Serializable] - public class TestTenantConnectionProvider : AbstractMultiTenantConnectionProvider - { - public TestTenantConnectionProvider(ISessionFactoryImplementor sfi, string tenantId, bool isSqlServerDialect) - { - TenantIdentifier = tenantId; - SessionFactory = sfi; - TenantConnectionString = sfi.ConnectionProvider.GetConnectionString(); - if (isSqlServerDialect) - { - var stringBuilder = new SqlConnectionStringBuilder(sfi.ConnectionProvider.GetConnectionString()); - stringBuilder.ApplicationName = tenantId; - TenantConnectionString = stringBuilder.ToString(); - } - } - - protected override string TenantConnectionString { get; } - - public override string TenantIdentifier { get; } - - protected override ISessionFactoryImplementor SessionFactory { get; } - } } diff --git a/src/NHibernate.Test/MultiTenancy/Entity.cs b/src/NHibernate.Test/MultiTenancy/Entity.cs new file mode 100644 index 00000000000..04636a58484 --- /dev/null +++ b/src/NHibernate.Test/MultiTenancy/Entity.cs @@ -0,0 +1,11 @@ +using System; + +namespace NHibernate.Test.MultiTenancy +{ + [Serializable] + class Entity + { + public virtual Guid Id { get; set; } + public virtual string Name { get; set; } + } +} diff --git a/src/NHibernate.Test/MultiTenancy/MockConnectionProvider.cs b/src/NHibernate.Test/MultiTenancy/MockConnectionProvider.cs new file mode 100644 index 00000000000..dc789d657db --- /dev/null +++ b/src/NHibernate.Test/MultiTenancy/MockConnectionProvider.cs @@ -0,0 +1,22 @@ +using NHibernate.Connection; +using NHibernate.MultiTenancy; + +namespace NHibernate.Test.MultiTenancy +{ + public class MockConnectionProvider : IMultiTenantConnectionProvider + { + private readonly IConnectionAccess _connectionAccess; + + public MockConnectionProvider(string tenantIdentifier, IConnectionAccess connectionAccess) + { + _connectionAccess = connectionAccess; + TenantIdentifier = tenantIdentifier; + } + + public string TenantIdentifier { get; } + public IConnectionAccess GetConnectionAccess() + { + return _connectionAccess; + } + } +} diff --git a/src/NHibernate.Test/MultiTenancy/TestTenantConnectionProvider.cs b/src/NHibernate.Test/MultiTenancy/TestTenantConnectionProvider.cs new file mode 100644 index 00000000000..1c034d3104e --- /dev/null +++ b/src/NHibernate.Test/MultiTenancy/TestTenantConnectionProvider.cs @@ -0,0 +1,31 @@ +using System; +using System.Data.SqlClient; +using NHibernate.Connection; +using NHibernate.Engine; +using NHibernate.MultiTenancy; + +namespace NHibernate.Test.MultiTenancy +{ + [Serializable] + public class TestTenantConnectionProvider : AbstractMultiTenantConnectionProvider + { + public TestTenantConnectionProvider(ISessionFactoryImplementor sfi, string tenantId, bool isSqlServerDialect) + { + TenantIdentifier = tenantId; + SessionFactory = sfi; + TenantConnectionString = sfi.ConnectionProvider.GetConnectionString(); + if (isSqlServerDialect) + { + var stringBuilder = new SqlConnectionStringBuilder(sfi.ConnectionProvider.GetConnectionString()); + stringBuilder.ApplicationName = tenantId; + TenantConnectionString = stringBuilder.ToString(); + } + } + + protected override string TenantConnectionString { get; } + + public override string TenantIdentifier { get; } + + protected override ISessionFactoryImplementor SessionFactory { get; } + } +} diff --git a/src/NHibernate/AdoNet/ConnectionManager.cs b/src/NHibernate/AdoNet/ConnectionManager.cs index b607f433502..8c5d1fa3aa9 100644 --- a/src/NHibernate/AdoNet/ConnectionManager.cs +++ b/src/NHibernate/AdoNet/ConnectionManager.cs @@ -84,7 +84,6 @@ public ConnectionManager( #pragma warning disable 618 : this(session, suppliedConnection, connectionReleaseMode, interceptor, shouldAutoJoinTransaction) #pragma warning restore 618 - { _connectionAccess = connectionAccess ?? throw new ArgumentNullException(nameof(connectionAccess)); } diff --git a/src/NHibernate/Async/Impl/AbstractSessionImpl.cs b/src/NHibernate/Async/Impl/AbstractSessionImpl.cs index 0c5f4f58e62..fb077b05d8b 100644 --- a/src/NHibernate/Async/Impl/AbstractSessionImpl.cs +++ b/src/NHibernate/Async/Impl/AbstractSessionImpl.cs @@ -17,7 +17,6 @@ using NHibernate.AdoNet; using NHibernate.Cache; using NHibernate.Collection; -using NHibernate.Connection; using NHibernate.Engine; using NHibernate.Engine.Query; using NHibernate.Engine.Query.Sql; @@ -220,16 +219,4 @@ protected async Task AfterOperationAsync(bool success, CancellationToken cancell public abstract Task ExecuteUpdateAsync(IQueryExpression queryExpression, QueryParameters queryParameters, CancellationToken cancellationToken); } - partial class NonContextualConnectionAccess : IConnectionAccess - { - - public Task GetConnectionAsync(CancellationToken cancellationToken) - { - if (cancellationToken.IsCancellationRequested) - { - return Task.FromCanceled(cancellationToken); - } - return _factory.ConnectionProvider.GetConnectionAsync(cancellationToken); - } - } } diff --git a/src/NHibernate/Async/Impl/NonContextualConnectionAccess.cs b/src/NHibernate/Async/Impl/NonContextualConnectionAccess.cs new file mode 100644 index 00000000000..7d43eeb64b7 --- /dev/null +++ b/src/NHibernate/Async/Impl/NonContextualConnectionAccess.cs @@ -0,0 +1,32 @@ +//------------------------------------------------------------------------------ +// +// 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; +using System.Data.Common; +using NHibernate.Connection; +using NHibernate.Engine; + +namespace NHibernate.Impl +{ + using System.Threading.Tasks; + using System.Threading; + partial class NonContextualConnectionAccess : IConnectionAccess + { + + public Task GetConnectionAsync(CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + return _factory.ConnectionProvider.GetConnectionAsync(cancellationToken); + } + } +} diff --git a/src/NHibernate/Cache/CacheKey.cs b/src/NHibernate/Cache/CacheKey.cs index 0187c3879e0..e0890cfe272 100644 --- a/src/NHibernate/Cache/CacheKey.cs +++ b/src/NHibernate/Cache/CacheKey.cs @@ -90,7 +90,7 @@ private int GenerateHashCode() if (_tenantIdentifier != null) { - hashCode = 37 * hashCode + _tenantIdentifier.GetHashCode(); + hashCode = (37 * hashCode) + _tenantIdentifier.GetHashCode(); } return hashCode; diff --git a/src/NHibernate/Cache/QueryKey.cs b/src/NHibernate/Cache/QueryKey.cs index 6ef92227c41..0af9bf4bc02 100644 --- a/src/NHibernate/Cache/QueryKey.cs +++ b/src/NHibernate/Cache/QueryKey.cs @@ -262,7 +262,7 @@ public int ComputeHashCode() result = 37 * result + _sqlQueryString.GetHashCode(); if (_tenantIdentifier != null) { - result = 37 * result + _tenantIdentifier.GetHashCode(); + result = (37 * result) + _tenantIdentifier.GetHashCode(); } return result; } diff --git a/src/NHibernate/Impl/AbstractSessionImpl.cs b/src/NHibernate/Impl/AbstractSessionImpl.cs index 029a1678ee9..16060dd5416 100644 --- a/src/NHibernate/Impl/AbstractSessionImpl.cs +++ b/src/NHibernate/Impl/AbstractSessionImpl.cs @@ -7,7 +7,6 @@ using NHibernate.AdoNet; using NHibernate.Cache; using NHibernate.Collection; -using NHibernate.Connection; using NHibernate.Engine; using NHibernate.Engine.Query; using NHibernate.Engine.Query.Sql; @@ -703,27 +702,4 @@ public virtual IQueryBatch CreateQueryBatch() return new QueryBatch(this, false); } } - - [Serializable] - partial class NonContextualConnectionAccess : IConnectionAccess - { - private readonly ISessionFactoryImplementor _factory; - - public NonContextualConnectionAccess(ISessionFactoryImplementor factory) - { - _factory = factory; - } - - public DbConnection GetConnection() - { - return _factory.ConnectionProvider.GetConnection(); - } - - public void CloseConnection(DbConnection connection) - { - _factory.ConnectionProvider.CloseConnection(connection); - } - - public string ConnectionString => _factory.ConnectionProvider.GetConnectionString(); - } } diff --git a/src/NHibernate/Impl/NonContextualConnectionAccess.cs b/src/NHibernate/Impl/NonContextualConnectionAccess.cs new file mode 100644 index 00000000000..4269297bbc0 --- /dev/null +++ b/src/NHibernate/Impl/NonContextualConnectionAccess.cs @@ -0,0 +1,30 @@ +using System; +using System.Data.Common; +using NHibernate.Connection; +using NHibernate.Engine; + +namespace NHibernate.Impl +{ + [Serializable] + partial class NonContextualConnectionAccess : IConnectionAccess + { + private readonly ISessionFactoryImplementor _factory; + + public NonContextualConnectionAccess(ISessionFactoryImplementor factory) + { + _factory = factory; + } + + public DbConnection GetConnection() + { + return _factory.ConnectionProvider.GetConnection(); + } + + public void CloseConnection(DbConnection connection) + { + _factory.ConnectionProvider.CloseConnection(connection); + } + + public string ConnectionString => _factory.ConnectionProvider.GetConnectionString(); + } +} From 16a596d8aa84e6b5aaa19807f14a6a064bb2c1e4 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Sun, 24 May 2020 23:19:05 +0300 Subject: [PATCH 13/27] whitespaces --- .../DatabaseStrategyNoDbSpecificFixture.cs | 20 ++++++++-------- .../DatabaseStrategyNoDbSpecificFixture.cs | 24 +++++++++---------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/NHibernate.Test/Async/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs b/src/NHibernate.Test/Async/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs index 74e65f2e186..f34352220de 100644 --- a/src/NHibernate.Test/Async/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs +++ b/src/NHibernate.Test/Async/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs @@ -127,7 +127,7 @@ public async Task QueryCacheSeparationPerTenantAsync() [Test] public async Task TenantSessionIsSerializableAndCanBeReconnectedAsync() { - ISession deserializedSession = null; + ISession deserializedSession = null; using (var sesTen1 = OpenTenantSession("tenant1")) { var entity = await (sesTen1.Query().WithOptions(x => x.SetCacheable(true)).Where(e => e.Id == _id).SingleOrDefaultAsync()); @@ -155,14 +155,14 @@ public async Task TenantSessionIsSerializableAndCanBeReconnectedAsync() Assert.That(Sfi.Statistics.SecondLevelCacheHitCount, Is.EqualTo(1)); } - private static string GetTenantId(ISession deserializedSession) + private static string GetTenantId(ISession session) { - return deserializedSession.GetSessionImplementation().GetTenantIdentifier(); + return session.GetSessionImplementation().GetTenantIdentifier(); } - - private static string GetTenantId(IStatelessSession deserializedSession) + + private static string GetTenantId(IStatelessSession session) { - return deserializedSession.GetSessionImplementation().GetTenantIdentifier(); + return session.GetSessionImplementation().GetTenantIdentifier(); } private T SpoofSerialization(T session) @@ -173,7 +173,7 @@ private T SpoofSerialization(T session) SurrogateSelector = new SerializationHelper.SurrogateSelector() #endif }; - MemoryStream stream = new MemoryStream(); + var stream = new MemoryStream(); formatter.Serialize(stream, session); stream.Position = 0; @@ -185,7 +185,7 @@ private ISession OpenTenantSession(string tenantId) { return Sfi.WithOptions().TenantConfiguration(GetTenantConfig(tenantId)).OpenSession(); } - + private IStatelessSession OpenTenantStatelessSession(string tenantId) { return Sfi.WithStatelessOptions().TenantConfiguration(GetTenantConfig(tenantId)).OpenStatelessSession(); @@ -227,8 +227,8 @@ protected override ISession OpenSession() protected override void OnTearDown() { - using (ISession session = OpenSession()) - using (ITransaction transaction = session.BeginTransaction()) + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) { session.Delete("from System.Object"); diff --git a/src/NHibernate.Test/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs b/src/NHibernate.Test/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs index 2f288ecd880..9fca4d70e6f 100644 --- a/src/NHibernate.Test/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs +++ b/src/NHibernate.Test/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs @@ -51,7 +51,7 @@ public void DifferentConnectionStringForDifferentTenants() if (!IsSqlServerDialect) Assert.Ignore("MSSqlServer specific test"); - using(var session1 = OpenTenantSession("tenant1")) + using (var session1 = OpenTenantSession("tenant1")) using (var session2 = OpenTenantSession("tenant2")) { Assert.That(session1.Connection.ConnectionString, Is.Not.EqualTo(session2.Connection.ConnectionString)); @@ -84,7 +84,7 @@ public void StatelessSessionDifferentConnectionStringForDifferentTenants() if (!IsSqlServerDialect) Assert.Ignore("MSSqlServer specific test"); - using(var session1 = OpenTenantStatelessSession("tenant1")) + using (var session1 = OpenTenantStatelessSession("tenant1")) using (var session2 = OpenTenantStatelessSession("tenant2")) { Assert.That(session1.Connection.ConnectionString, Is.Not.EqualTo(session2.Connection.ConnectionString)); @@ -216,7 +216,7 @@ public void QueryCacheSeparationPerTenant() [Test] public void TenantSessionIsSerializableAndCanBeReconnected() { - ISession deserializedSession = null; + ISession deserializedSession = null; using (var sesTen1 = OpenTenantSession("tenant1")) { var entity = sesTen1.Query().WithOptions(x => x.SetCacheable(true)).Where(e => e.Id == _id).SingleOrDefault(); @@ -244,14 +244,14 @@ public void TenantSessionIsSerializableAndCanBeReconnected() Assert.That(Sfi.Statistics.SecondLevelCacheHitCount, Is.EqualTo(1)); } - private static string GetTenantId(ISession deserializedSession) + private static string GetTenantId(ISession session) { - return deserializedSession.GetSessionImplementation().GetTenantIdentifier(); + return session.GetSessionImplementation().GetTenantIdentifier(); } - - private static string GetTenantId(IStatelessSession deserializedSession) + + private static string GetTenantId(IStatelessSession session) { - return deserializedSession.GetSessionImplementation().GetTenantIdentifier(); + return session.GetSessionImplementation().GetTenantIdentifier(); } private T SpoofSerialization(T session) @@ -262,7 +262,7 @@ private T SpoofSerialization(T session) SurrogateSelector = new SerializationHelper.SurrogateSelector() #endif }; - MemoryStream stream = new MemoryStream(); + var stream = new MemoryStream(); formatter.Serialize(stream, session); stream.Position = 0; @@ -274,7 +274,7 @@ private ISession OpenTenantSession(string tenantId) { return Sfi.WithOptions().TenantConfiguration(GetTenantConfig(tenantId)).OpenSession(); } - + private IStatelessSession OpenTenantStatelessSession(string tenantId) { return Sfi.WithStatelessOptions().TenantConfiguration(GetTenantConfig(tenantId)).OpenStatelessSession(); @@ -316,8 +316,8 @@ protected override ISession OpenSession() protected override void OnTearDown() { - using (ISession session = OpenSession()) - using (ITransaction transaction = session.BeginTransaction()) + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) { session.Delete("from System.Object"); From 8c8094166a131dc07197820b3bc024d5b1d82fbc Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Mon, 8 Jun 2020 13:12:01 +0300 Subject: [PATCH 14/27] fix build after merge --- src/NHibernate/Cache/QueryKey.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NHibernate/Cache/QueryKey.cs b/src/NHibernate/Cache/QueryKey.cs index 0af9bf4bc02..83f97533437 100644 --- a/src/NHibernate/Cache/QueryKey.cs +++ b/src/NHibernate/Cache/QueryKey.cs @@ -194,7 +194,7 @@ public bool Equals(QueryKey other) return false; } - if (_tenantIdentifier != that._tenantIdentifier) + if (_tenantIdentifier != other._tenantIdentifier) { return false; } From d628e844dc950876c3a640ef1c9b503c536e97a7 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Mon, 22 Jun 2020 19:38:59 +0300 Subject: [PATCH 15/27] Move IMultiTenancyConnectionProvider to session factory configuration --- .../DatabaseStrategyNoDbSpecificFixture.cs | 16 +++++- .../DatabaseStrategyNoDbSpecificFixture.cs | 36 +++++------- .../MultiTenancy/MockConnectionProvider.cs | 22 ------- .../TestMultiTenancyConnectionProvider.cs | 17 ++++++ .../MultiTenancy/TestTenantConfiguration.cs | 16 ++++++ .../TestTenantConnectionProvider.cs | 31 ---------- src/NHibernate/AdoNet/ConnectionManager.cs | 6 +- .../Async/AdoNet/ConnectionManager.cs | 2 +- .../Async/Connection/IConnectionAccess.cs | 2 +- .../Impl/NonContextualConnectionAccess.cs | 5 +- ... DefaultMultiTenancyConnectionProvider.cs} | 11 ++-- src/NHibernate/Cfg/Environment.cs | 5 ++ .../DbIntegrationConfigurationProperties.cs | 6 ++ src/NHibernate/Cfg/Settings.cs | 2 + src/NHibernate/Cfg/SettingsFactory.cs | 31 +++++++++- .../Connection/IConnectionAccess.cs | 4 +- src/NHibernate/Impl/AbstractSessionImpl.cs | 9 +-- .../Impl/NonContextualConnectionAccess.cs | 18 +++--- .../AbstractMultiTenantConnectionProvider.cs | 57 ------------------- .../DefaultMultiTenancyConnectionProvider.cs | 55 ++++++++++++++++++ .../IMultiTenancyConnectionProvider.cs | 16 ++++++ .../IMultiTenantConnectionProvider.cs | 22 ------- .../MultiTenancy/TenantConfiguration.cs | 13 ++--- src/NHibernate/nhibernate-configuration.xsd | 14 +++++ 24 files changed, 217 insertions(+), 199 deletions(-) delete mode 100644 src/NHibernate.Test/MultiTenancy/MockConnectionProvider.cs create mode 100644 src/NHibernate.Test/MultiTenancy/TestMultiTenancyConnectionProvider.cs create mode 100644 src/NHibernate.Test/MultiTenancy/TestTenantConfiguration.cs delete mode 100644 src/NHibernate.Test/MultiTenancy/TestTenantConnectionProvider.cs rename src/NHibernate/Async/MultiTenancy/{AbstractMultiTenantConnectionProvider.cs => DefaultMultiTenancyConnectionProvider.cs} (68%) delete mode 100644 src/NHibernate/MultiTenancy/AbstractMultiTenantConnectionProvider.cs create mode 100644 src/NHibernate/MultiTenancy/DefaultMultiTenancyConnectionProvider.cs create mode 100644 src/NHibernate/MultiTenancy/IMultiTenancyConnectionProvider.cs delete mode 100644 src/NHibernate/MultiTenancy/IMultiTenantConnectionProvider.cs diff --git a/src/NHibernate.Test/Async/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs b/src/NHibernate.Test/Async/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs index f34352220de..59db07753a0 100644 --- a/src/NHibernate.Test/Async/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs +++ b/src/NHibernate.Test/Async/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs @@ -16,6 +16,7 @@ using System.Runtime.Serialization.Formatters.Binary; using NHibernate.Cfg; using NHibernate.Cfg.MappingSchema; +using NHibernate.Connection; using NHibernate.Dialect; using NHibernate.Driver; using NHibernate.Engine; @@ -35,7 +36,12 @@ public class DatabaseStrategyNoDbSpecificFixtureAsync : TestCaseMappingByCode protected override void Configure(Configuration configuration) { - configuration.Properties[Cfg.Environment.MultiTenancy] = MultiTenancyStrategy.Database.ToString(); + configuration.DataBaseIntegration( + x => + { + x.MultiTenancy = MultiTenancyStrategy.Database; + x.MultiTenancyConnectionProvider(); + }); configuration.Properties[Cfg.Environment.GenerateStatistics] = "true"; base.Configure(configuration); } @@ -193,7 +199,10 @@ private IStatelessSession OpenTenantStatelessSession(string tenantId) private TenantConfiguration GetTenantConfig(string tenantId) { - return new TenantConfiguration(new TestTenantConnectionProvider(Sfi, tenantId, IsSqlServerDialect)); + return new TestTenantConfiguration(tenantId, IsSqlServerDialect) + { + ConnectionString = Sfi.ConnectionProvider.GetConnectionString() + }; } private bool IsSqlServerDialect => Sfi.Dialect is MsSql2000Dialect && !(Sfi.ConnectionProvider.Driver is OdbcDriver); @@ -217,7 +226,8 @@ protected override HbmMapping GetMappings() protected override DbConnection OpenConnectionForSchemaExport() { - return GetTenantConfig("defaultTenant").ConnectionAccess.GetConnection(); + return Sfi.Settings.MultiTenancyConnectionProvider + .GetConnectionAccess(GetTenantConfig("defaultTenant")).GetConnection(Sfi.ConnectionProvider); } protected override ISession OpenSession() diff --git a/src/NHibernate.Test/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs b/src/NHibernate.Test/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs index 9fca4d70e6f..5d2f0d69d81 100644 --- a/src/NHibernate.Test/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs +++ b/src/NHibernate.Test/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs @@ -6,6 +6,7 @@ using System.Runtime.Serialization.Formatters.Binary; using NHibernate.Cfg; using NHibernate.Cfg.MappingSchema; +using NHibernate.Connection; using NHibernate.Dialect; using NHibernate.Driver; using NHibernate.Engine; @@ -24,7 +25,12 @@ public class DatabaseStrategyNoDbSpecificFixture : TestCaseMappingByCode protected override void Configure(Configuration configuration) { - configuration.Properties[Cfg.Environment.MultiTenancy] = MultiTenancyStrategy.Database.ToString(); + configuration.DataBaseIntegration( + x => + { + x.MultiTenancy = MultiTenancyStrategy.Database; + x.MultiTenancyConnectionProvider(); + }); configuration.Properties[Cfg.Environment.GenerateStatistics] = "true"; base.Configure(configuration); } @@ -32,15 +38,7 @@ protected override void Configure(Configuration configuration) [Test] public void ShouldThrowWithNoTenantIdentifier() { - var sessionBuilder = Sfi.WithOptions().TenantConfiguration(new TenantConfiguration(new MockConnectionProvider(null, null))); - - Assert.That(() => sessionBuilder.OpenSession(), Throws.ArgumentException); - } - - [Test] - public void ShouldThrowWithNoConnectionAccess() - { - var sessionBuilder = Sfi.WithOptions().TenantConfiguration(new TenantConfiguration(new MockConnectionProvider("tenant1", null))); + var sessionBuilder = Sfi.WithOptions().TenantConfiguration(new TenantConfiguration(null)); Assert.That(() => sessionBuilder.OpenSession(), Throws.ArgumentException); } @@ -65,19 +63,11 @@ public void DifferentConnectionStringForDifferentTenants() [Test] public void StatelessSessionShouldThrowWithNoTenantIdentifier() { - var sessionBuilder = Sfi.WithStatelessOptions().TenantConfiguration(new TenantConfiguration(new MockConnectionProvider(null, null))); + var sessionBuilder = Sfi.WithStatelessOptions().TenantConfiguration(new TenantConfiguration(null)); Assert.That(() => sessionBuilder.OpenStatelessSession(), Throws.ArgumentException); } - [Test] - public void StatelessSessionShouldThrowWithNoConnectionAccess() - { - var sessionBuilder = Sfi.WithStatelessOptions().TenantConfiguration(new TenantConfiguration(new MockConnectionProvider("tenant1", null))); - - Assert.That(() => sessionBuilder.OpenStatelessSession(), Throws.ArgumentException); - } - [Test] public void StatelessSessionDifferentConnectionStringForDifferentTenants() { @@ -282,7 +272,10 @@ private IStatelessSession OpenTenantStatelessSession(string tenantId) private TenantConfiguration GetTenantConfig(string tenantId) { - return new TenantConfiguration(new TestTenantConnectionProvider(Sfi, tenantId, IsSqlServerDialect)); + return new TestTenantConfiguration(tenantId, IsSqlServerDialect) + { + ConnectionString = Sfi.ConnectionProvider.GetConnectionString() + }; } private bool IsSqlServerDialect => Sfi.Dialect is MsSql2000Dialect && !(Sfi.ConnectionProvider.Driver is OdbcDriver); @@ -306,7 +299,8 @@ protected override HbmMapping GetMappings() protected override DbConnection OpenConnectionForSchemaExport() { - return GetTenantConfig("defaultTenant").ConnectionAccess.GetConnection(); + return Sfi.Settings.MultiTenancyConnectionProvider + .GetConnectionAccess(GetTenantConfig("defaultTenant")).GetConnection(Sfi.ConnectionProvider); } protected override ISession OpenSession() diff --git a/src/NHibernate.Test/MultiTenancy/MockConnectionProvider.cs b/src/NHibernate.Test/MultiTenancy/MockConnectionProvider.cs deleted file mode 100644 index dc789d657db..00000000000 --- a/src/NHibernate.Test/MultiTenancy/MockConnectionProvider.cs +++ /dev/null @@ -1,22 +0,0 @@ -using NHibernate.Connection; -using NHibernate.MultiTenancy; - -namespace NHibernate.Test.MultiTenancy -{ - public class MockConnectionProvider : IMultiTenantConnectionProvider - { - private readonly IConnectionAccess _connectionAccess; - - public MockConnectionProvider(string tenantIdentifier, IConnectionAccess connectionAccess) - { - _connectionAccess = connectionAccess; - TenantIdentifier = tenantIdentifier; - } - - public string TenantIdentifier { get; } - public IConnectionAccess GetConnectionAccess() - { - return _connectionAccess; - } - } -} diff --git a/src/NHibernate.Test/MultiTenancy/TestMultiTenancyConnectionProvider.cs b/src/NHibernate.Test/MultiTenancy/TestMultiTenancyConnectionProvider.cs new file mode 100644 index 00000000000..ee6dc054cc4 --- /dev/null +++ b/src/NHibernate.Test/MultiTenancy/TestMultiTenancyConnectionProvider.cs @@ -0,0 +1,17 @@ +using System; +using System.Data.SqlClient; +using NHibernate.MultiTenancy; + +namespace NHibernate.Test.MultiTenancy +{ + [Serializable] + public class TestMultiTenancyConnectionProvider : DefaultMultiTenancyConnectionProvider + { + protected override string GetTenantConnectionString(TenantConfiguration configuration) + { + return configuration is TestTenantConfiguration tenant && tenant.IsSqlServerDialect + ? new SqlConnectionStringBuilder(configuration.ConnectionString) {ApplicationName = configuration.TenantIdentifier}.ToString() + : base.GetTenantConnectionString(configuration); + } + } +} diff --git a/src/NHibernate.Test/MultiTenancy/TestTenantConfiguration.cs b/src/NHibernate.Test/MultiTenancy/TestTenantConfiguration.cs new file mode 100644 index 00000000000..b743cc95ae0 --- /dev/null +++ b/src/NHibernate.Test/MultiTenancy/TestTenantConfiguration.cs @@ -0,0 +1,16 @@ +using System; +using NHibernate.MultiTenancy; + +namespace NHibernate.Test.MultiTenancy +{ + [Serializable] + public class TestTenantConfiguration : TenantConfiguration + { + public TestTenantConfiguration(string tenantIdentifier, bool isSqlServerDialect) : base(tenantIdentifier) + { + IsSqlServerDialect = isSqlServerDialect; + } + + public bool IsSqlServerDialect { get; } + } +} diff --git a/src/NHibernate.Test/MultiTenancy/TestTenantConnectionProvider.cs b/src/NHibernate.Test/MultiTenancy/TestTenantConnectionProvider.cs deleted file mode 100644 index 1c034d3104e..00000000000 --- a/src/NHibernate.Test/MultiTenancy/TestTenantConnectionProvider.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; -using System.Data.SqlClient; -using NHibernate.Connection; -using NHibernate.Engine; -using NHibernate.MultiTenancy; - -namespace NHibernate.Test.MultiTenancy -{ - [Serializable] - public class TestTenantConnectionProvider : AbstractMultiTenantConnectionProvider - { - public TestTenantConnectionProvider(ISessionFactoryImplementor sfi, string tenantId, bool isSqlServerDialect) - { - TenantIdentifier = tenantId; - SessionFactory = sfi; - TenantConnectionString = sfi.ConnectionProvider.GetConnectionString(); - if (isSqlServerDialect) - { - var stringBuilder = new SqlConnectionStringBuilder(sfi.ConnectionProvider.GetConnectionString()); - stringBuilder.ApplicationName = tenantId; - TenantConnectionString = stringBuilder.ToString(); - } - } - - protected override string TenantConnectionString { get; } - - public override string TenantIdentifier { get; } - - protected override ISessionFactoryImplementor SessionFactory { get; } - } -} diff --git a/src/NHibernate/AdoNet/ConnectionManager.cs b/src/NHibernate/AdoNet/ConnectionManager.cs index 8c5d1fa3aa9..00912cb81c4 100644 --- a/src/NHibernate/AdoNet/ConnectionManager.cs +++ b/src/NHibernate/AdoNet/ConnectionManager.cs @@ -165,7 +165,7 @@ public DbConnection Close() if (_backupConnection != null) { _log.Warn("Backup connection was still defined at time of closing."); - _connectionAccess.CloseConnection(_backupConnection); + _connectionAccess.CloseConnection(_backupConnection, Factory.ConnectionProvider); _backupConnection = null; } @@ -222,7 +222,7 @@ public DbConnection Disconnect() private void CloseConnection() { - _connectionAccess.CloseConnection(_connection); + _connectionAccess.CloseConnection(_connection, Factory.ConnectionProvider); _connection = null; } @@ -256,7 +256,7 @@ public DbConnection GetConnection() { if (_ownConnection) { - _connection = _connectionAccess.GetConnection(); + _connection = _connectionAccess.GetConnection(Factory.ConnectionProvider); // 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 a716845c6e9..670d4c59faf 100644 --- a/src/NHibernate/Async/AdoNet/ConnectionManager.cs +++ b/src/NHibernate/Async/AdoNet/ConnectionManager.cs @@ -61,7 +61,7 @@ async Task InternalGetConnectionAsync() { if (_ownConnection) { - _connection = await (_connectionAccess.GetConnectionAsync(cancellationToken)).ConfigureAwait(false); + _connection = await (_connectionAccess.GetConnectionAsync(Factory.ConnectionProvider, 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/Connection/IConnectionAccess.cs b/src/NHibernate/Async/Connection/IConnectionAccess.cs index 6cfb3c5acbc..2fea116ad20 100644 --- a/src/NHibernate/Async/Connection/IConnectionAccess.cs +++ b/src/NHibernate/Async/Connection/IConnectionAccess.cs @@ -17,6 +17,6 @@ namespace NHibernate.Connection public partial interface IConnectionAccess { //ObtainConnection in hibernate - Task GetConnectionAsync(CancellationToken cancellationToken); + Task GetConnectionAsync(IConnectionProvider provider, CancellationToken cancellationToken); } } diff --git a/src/NHibernate/Async/Impl/NonContextualConnectionAccess.cs b/src/NHibernate/Async/Impl/NonContextualConnectionAccess.cs index 7d43eeb64b7..7078ad828f5 100644 --- a/src/NHibernate/Async/Impl/NonContextualConnectionAccess.cs +++ b/src/NHibernate/Async/Impl/NonContextualConnectionAccess.cs @@ -11,7 +11,6 @@ using System; using System.Data.Common; using NHibernate.Connection; -using NHibernate.Engine; namespace NHibernate.Impl { @@ -20,13 +19,13 @@ namespace NHibernate.Impl partial class NonContextualConnectionAccess : IConnectionAccess { - public Task GetConnectionAsync(CancellationToken cancellationToken) + public Task GetConnectionAsync(IConnectionProvider provider, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) { return Task.FromCanceled(cancellationToken); } - return _factory.ConnectionProvider.GetConnectionAsync(cancellationToken); + return provider.GetConnectionAsync(ConnectionString, cancellationToken); } } } diff --git a/src/NHibernate/Async/MultiTenancy/AbstractMultiTenantConnectionProvider.cs b/src/NHibernate/Async/MultiTenancy/DefaultMultiTenancyConnectionProvider.cs similarity index 68% rename from src/NHibernate/Async/MultiTenancy/AbstractMultiTenantConnectionProvider.cs rename to src/NHibernate/Async/MultiTenancy/DefaultMultiTenancyConnectionProvider.cs index 96c325c7d3e..7483d0a6bc2 100644 --- a/src/NHibernate/Async/MultiTenancy/AbstractMultiTenantConnectionProvider.cs +++ b/src/NHibernate/Async/MultiTenancy/DefaultMultiTenancyConnectionProvider.cs @@ -11,26 +11,25 @@ using System; using System.Data.Common; using NHibernate.Connection; -using NHibernate.Engine; -using NHibernate.Util; +using Environment = NHibernate.Cfg.Environment; namespace NHibernate.MultiTenancy { using System.Threading.Tasks; using System.Threading; - public abstract partial class AbstractMultiTenantConnectionProvider : IMultiTenantConnectionProvider + public partial class DefaultMultiTenancyConnectionProvider : IMultiTenancyConnectionProvider { partial class ContextualConnectionAccess : IConnectionAccess { - public Task GetConnectionAsync(CancellationToken cancellationToken) + public Task GetConnectionAsync(IConnectionProvider provider, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) { return Task.FromCanceled(cancellationToken); } - return _factory.ConnectionProvider.GetConnectionAsync(ConnectionString, cancellationToken); + return provider.GetConnectionAsync(ConnectionString, cancellationToken); } - } + } } } diff --git a/src/NHibernate/Cfg/Environment.cs b/src/NHibernate/Cfg/Environment.cs index f9df5d115a6..2c48dbde3c0 100644 --- a/src/NHibernate/Cfg/Environment.cs +++ b/src/NHibernate/Cfg/Environment.cs @@ -391,6 +391,11 @@ public static string Version /// public const string MultiTenancy = "multiTenancy"; + /// + /// Connection provider for given multi-tenancy strategy. + /// + public const string MultiTenancyConnectionProvider = "multiTenancy.connection.provider"; + private static IBytecodeProvider BytecodeProviderInstance; private static bool EnableReflectionOptimizer; diff --git a/src/NHibernate/Cfg/Loquacious/DbIntegrationConfigurationProperties.cs b/src/NHibernate/Cfg/Loquacious/DbIntegrationConfigurationProperties.cs index 355f12f7b0f..f19619bc251 100644 --- a/src/NHibernate/Cfg/Loquacious/DbIntegrationConfigurationProperties.cs +++ b/src/NHibernate/Cfg/Loquacious/DbIntegrationConfigurationProperties.cs @@ -4,6 +4,7 @@ using NHibernate.Driver; using NHibernate.Exceptions; using NHibernate.Linq.Visitors; +using NHibernate.MultiTenancy; using NHibernate.Transaction; namespace NHibernate.Cfg.Loquacious @@ -158,6 +159,11 @@ public MultiTenancy.MultiTenancyStrategy MultiTenancy set { configuration.SetProperty(Environment.MultiTenancy, value.ToString()); } } + public void MultiTenancyConnectionProvider() where TProvider : IMultiTenancyConnectionProvider + { + configuration.SetProperty(Environment.MultiTenancyConnectionProvider, typeof(TProvider).AssemblyQualifiedName); + } + #endregion } } diff --git a/src/NHibernate/Cfg/Settings.cs b/src/NHibernate/Cfg/Settings.cs index 20717d128fa..f973766013e 100644 --- a/src/NHibernate/Cfg/Settings.cs +++ b/src/NHibernate/Cfg/Settings.cs @@ -210,5 +210,7 @@ internal string GetFullCacheRegionName(string name) } public MultiTenancyStrategy MultiTenancyStrategy { get; internal set; } + + public IMultiTenancyConnectionProvider MultiTenancyConnectionProvider { get; internal set; } } } diff --git a/src/NHibernate/Cfg/SettingsFactory.cs b/src/NHibernate/Cfg/SettingsFactory.cs index 7b5cb5d9372..84ae6c48194 100644 --- a/src/NHibernate/Cfg/SettingsFactory.cs +++ b/src/NHibernate/Cfg/SettingsFactory.cs @@ -327,9 +327,12 @@ public Settings BuildSettings(IDictionary properties) settings.TrackSessionId = trackSessionId; var multiTenancyStrategy = PropertiesHelper.GetEnum(Environment.MultiTenancy, properties, MultiTenancyStrategy.None); - if(multiTenancyStrategy != MultiTenancyStrategy.None) - log.Debug("multi-tenancy strategy : " + multiTenancyStrategy); settings.MultiTenancyStrategy = multiTenancyStrategy; + if (multiTenancyStrategy != MultiTenancyStrategy.None) + { + log.Debug("multi-tenancy strategy : " + multiTenancyStrategy); + settings.MultiTenancyConnectionProvider = CreateMultiTenancyConnectionProvider(properties); + } return settings; } @@ -417,6 +420,30 @@ private static System.Type CreateLinqQueryProviderType(IDictionary properties) + { + string className = PropertiesHelper.GetString( + Environment.MultiTenancyConnectionProvider, + properties, + null); + if (className == null) + { + log.Info("Default Multi-tenancy connection provider is used: {0}", typeof(DefaultMultiTenancyConnectionProvider).FullName); + return new DefaultMultiTenancyConnectionProvider(); + } + + log.Info("Multi-tenancy connection provider: {0}", className); + try + { + return (IMultiTenancyConnectionProvider) + Environment.ObjectsFactory.CreateInstance(System.Type.GetType(className, true)); + } + catch (Exception cnfe) + { + throw new HibernateException("could not find Multi-tenancy connection provider class: " + className, cnfe); + } + } + private static ITransactionFactory CreateTransactionFactory(IDictionary properties) { string className = PropertiesHelper.GetString( diff --git a/src/NHibernate/Connection/IConnectionAccess.cs b/src/NHibernate/Connection/IConnectionAccess.cs index 58b2b361a06..fd683aea519 100644 --- a/src/NHibernate/Connection/IConnectionAccess.cs +++ b/src/NHibernate/Connection/IConnectionAccess.cs @@ -11,10 +11,10 @@ namespace NHibernate.Connection public partial interface IConnectionAccess { //ObtainConnection in hibernate - DbConnection GetConnection(); + DbConnection GetConnection(IConnectionProvider provider); //ReleaseConnection in hibernate - void CloseConnection(DbConnection conn); + void CloseConnection(DbConnection conn, IConnectionProvider provider); string ConnectionString { get; } } diff --git a/src/NHibernate/Impl/AbstractSessionImpl.cs b/src/NHibernate/Impl/AbstractSessionImpl.cs index 16060dd5416..6255dc202f5 100644 --- a/src/NHibernate/Impl/AbstractSessionImpl.cs +++ b/src/NHibernate/Impl/AbstractSessionImpl.cs @@ -70,11 +70,6 @@ private TenantConfiguration ValidateTenantConfiguration(ISessionFactoryImplement throw new ArgumentException("Tenant configuration with TenantIdentifier defined is required for multi-tenancy.", nameof(tenantConfiguration)); } - if (factory.Settings.MultiTenancyStrategy == MultiTenancyStrategy.Database && tenantConfiguration.ConnectionAccess == null) - { - throw new ArgumentException($"Tenant configuration with ConnectionAccess defined is required for {factory.Settings.MultiTenancyStrategy} multi-tenancy strategy."); - } - return tenantConfiguration; } @@ -107,7 +102,9 @@ protected internal AbstractSessionImpl(ISessionFactoryImplementor factory, ISess options.UserSuppliedConnection, options.SessionConnectionReleaseMode, Interceptor, - _tenantConfiguration?.ConnectionAccess ?? new NonContextualConnectionAccess(_factory), + _tenantConfiguration == null + ? new NonContextualConnectionAccess(_factory.ConnectionProvider) + : _factory.Settings.MultiTenancyConnectionProvider.GetConnectionAccess(_tenantConfiguration), options.ShouldAutoJoinTransaction); } } diff --git a/src/NHibernate/Impl/NonContextualConnectionAccess.cs b/src/NHibernate/Impl/NonContextualConnectionAccess.cs index 4269297bbc0..fe0536b2da9 100644 --- a/src/NHibernate/Impl/NonContextualConnectionAccess.cs +++ b/src/NHibernate/Impl/NonContextualConnectionAccess.cs @@ -1,30 +1,28 @@ using System; using System.Data.Common; using NHibernate.Connection; -using NHibernate.Engine; namespace NHibernate.Impl { [Serializable] partial class NonContextualConnectionAccess : IConnectionAccess { - private readonly ISessionFactoryImplementor _factory; - - public NonContextualConnectionAccess(ISessionFactoryImplementor factory) + public NonContextualConnectionAccess(IConnectionProvider provider) { - _factory = factory; + ConnectionString = provider.GetConnectionString(); + ; } - public DbConnection GetConnection() + public DbConnection GetConnection(IConnectionProvider provider) { - return _factory.ConnectionProvider.GetConnection(); + return provider.GetConnection(ConnectionString); } - public void CloseConnection(DbConnection connection) + public void CloseConnection(DbConnection conn, IConnectionProvider provider) { - _factory.ConnectionProvider.CloseConnection(connection); + provider.CloseConnection(conn); } - public string ConnectionString => _factory.ConnectionProvider.GetConnectionString(); + public string ConnectionString { get; } } } diff --git a/src/NHibernate/MultiTenancy/AbstractMultiTenantConnectionProvider.cs b/src/NHibernate/MultiTenancy/AbstractMultiTenantConnectionProvider.cs deleted file mode 100644 index 7dfced1671e..00000000000 --- a/src/NHibernate/MultiTenancy/AbstractMultiTenantConnectionProvider.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System; -using System.Data.Common; -using NHibernate.Connection; -using NHibernate.Engine; -using NHibernate.Util; - -namespace NHibernate.MultiTenancy -{ - /// - /// Base implementation for multi-tenancy strategy - /// - [Serializable] - public abstract partial class AbstractMultiTenantConnectionProvider : IMultiTenantConnectionProvider - { - /// - /// Tenant connection string - /// - protected abstract string TenantConnectionString { get; } - - /// - public abstract string TenantIdentifier { get; } - - /// - public IConnectionAccess GetConnectionAccess() - { - //TODO 6.0: Remove check - ReflectHelper.CastOrThrow(SessionFactory.ConnectionProvider, $"multi-tenancy. For custom connection provider please implement IMultiTenantConnectionProvider directly for '{GetType().Name}' and do not use {nameof(AbstractMultiTenantConnectionProvider)} as base class."); - return new ContextualConnectionAccess(TenantConnectionString, SessionFactory); - } - - protected abstract ISessionFactoryImplementor SessionFactory { get; } - - [Serializable] - partial class ContextualConnectionAccess : IConnectionAccess - { - private readonly ISessionFactoryImplementor _factory; - - public ContextualConnectionAccess(string connectionString, ISessionFactoryImplementor factory) - { - _factory = factory; - ConnectionString = connectionString; - } - - public DbConnection GetConnection() - { - return _factory.ConnectionProvider.GetConnection(ConnectionString); - } - - public void CloseConnection(DbConnection connection) - { - _factory.ConnectionProvider.CloseConnection(connection); - } - - public string ConnectionString { get; } - } - } -} diff --git a/src/NHibernate/MultiTenancy/DefaultMultiTenancyConnectionProvider.cs b/src/NHibernate/MultiTenancy/DefaultMultiTenancyConnectionProvider.cs new file mode 100644 index 00000000000..f9bfbb3f0ac --- /dev/null +++ b/src/NHibernate/MultiTenancy/DefaultMultiTenancyConnectionProvider.cs @@ -0,0 +1,55 @@ +using System; +using System.Data.Common; +using NHibernate.Connection; +using Environment = NHibernate.Cfg.Environment; + +namespace NHibernate.MultiTenancy +{ + /// + /// Base implementation for multi-tenancy strategy + /// + [Serializable] + public partial class DefaultMultiTenancyConnectionProvider : IMultiTenancyConnectionProvider + { + /// + public IConnectionAccess GetConnectionAccess(TenantConfiguration configuration) + { + var tenantConnectionString = GetTenantConnectionString(configuration); + if (string.IsNullOrEmpty(tenantConnectionString)) + { + throw new HibernateException( + message: $"Tenant '{configuration.TenantIdentifier}' connection string is empty." + + $" Either provide it on each session opening in TenantConfiguration.ConnectionString or" + + $" provide custom IMultiTenantConnectionProvider via '{Environment.MultiTenancyConnectionProvider}` session factory setting."); + } + + return new ContextualConnectionAccess(tenantConnectionString); + } + + protected virtual string GetTenantConnectionString(TenantConfiguration configuration) + { + return configuration.ConnectionString; + } + + [Serializable] + partial class ContextualConnectionAccess : IConnectionAccess + { + public ContextualConnectionAccess(string connectionString) + { + ConnectionString = connectionString; + } + + public DbConnection GetConnection(IConnectionProvider provider) + { + return provider.GetConnection(ConnectionString); + } + + public void CloseConnection(DbConnection conn, IConnectionProvider provider) + { + provider.CloseConnection(conn); + } + + public string ConnectionString { get; } + } + } +} diff --git a/src/NHibernate/MultiTenancy/IMultiTenancyConnectionProvider.cs b/src/NHibernate/MultiTenancy/IMultiTenancyConnectionProvider.cs new file mode 100644 index 00000000000..d875034b444 --- /dev/null +++ b/src/NHibernate/MultiTenancy/IMultiTenancyConnectionProvider.cs @@ -0,0 +1,16 @@ +using NHibernate.Connection; + +namespace NHibernate.MultiTenancy +{ + /// + /// A specialized Connection provider contract used when the application is using multi-tenancy support requiring + /// tenant aware connections. + /// + public interface IMultiTenancyConnectionProvider + { + /// + /// Tenant connection access + /// + IConnectionAccess GetConnectionAccess(TenantConfiguration configuration); + } +} diff --git a/src/NHibernate/MultiTenancy/IMultiTenantConnectionProvider.cs b/src/NHibernate/MultiTenancy/IMultiTenantConnectionProvider.cs deleted file mode 100644 index b8ef18f1478..00000000000 --- a/src/NHibernate/MultiTenancy/IMultiTenantConnectionProvider.cs +++ /dev/null @@ -1,22 +0,0 @@ -using NHibernate.Connection; - -namespace NHibernate.MultiTenancy -{ - /// - /// A specialized Connection provider contract used when the application is using multi-tenancy support requiring - /// tenant aware connections. - /// - public interface IMultiTenantConnectionProvider - { - /// - /// Tenant identifier must uniquely identify tenant (is used for data separation in cache) - /// Note: Among other things this value is used for data separation between tenants in cache so not unique value will leak data to other tenants - /// - string TenantIdentifier { get; } - - /// - /// Tenant connection access - /// - IConnectionAccess GetConnectionAccess(); - } -} diff --git a/src/NHibernate/MultiTenancy/TenantConfiguration.cs b/src/NHibernate/MultiTenancy/TenantConfiguration.cs index e84d95163cc..4f9001d565e 100644 --- a/src/NHibernate/MultiTenancy/TenantConfiguration.cs +++ b/src/NHibernate/MultiTenancy/TenantConfiguration.cs @@ -1,5 +1,4 @@ using System; -using NHibernate.Connection; namespace NHibernate.MultiTenancy { @@ -16,17 +15,13 @@ public class TenantConfiguration public string TenantIdentifier { get; set; } /// - /// Tenant connection access. Required for multi-tenancy strategy. + /// Tenant Connection String. Usage depends on multi-tenancy connection provider "multiTenancy.connection.provider" /// - public IConnectionAccess ConnectionAccess { get; set; } + public string ConnectionString { get; set; } - /// - /// Creates tenant configuration for multi-tenancy strategy. - /// - public TenantConfiguration(IMultiTenantConnectionProvider tenantConnectionProvider) + public TenantConfiguration(string tenantIdentifier) { - TenantIdentifier = tenantConnectionProvider.TenantIdentifier; - ConnectionAccess = tenantConnectionProvider.GetConnectionAccess(); + TenantIdentifier = tenantIdentifier; } } } diff --git a/src/NHibernate/nhibernate-configuration.xsd b/src/NHibernate/nhibernate-configuration.xsd index 9bfd1e9f7b9..fc8f753b1a4 100644 --- a/src/NHibernate/nhibernate-configuration.xsd +++ b/src/NHibernate/nhibernate-configuration.xsd @@ -336,6 +336,20 @@ + + + + Strategy for multi-tenancy. See MultiTenancyStrategy enum for supported values + + + + + + + Connection provider for given multi-tenancy strategy. + + + From 6d11bad0127752114348d8b61e11796e362a8e59 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Mon, 22 Jun 2020 21:01:37 +0300 Subject: [PATCH 16/27] Fix CodeFactor issues --- src/NHibernate/Async/Impl/AbstractSessionImpl.cs | 1 + src/NHibernate/Impl/AbstractSessionImpl.cs | 3 ++- src/NHibernate/Impl/NonContextualConnectionAccess.cs | 5 ++--- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/NHibernate/Async/Impl/AbstractSessionImpl.cs b/src/NHibernate/Async/Impl/AbstractSessionImpl.cs index fb077b05d8b..ed3948ad38e 100644 --- a/src/NHibernate/Async/Impl/AbstractSessionImpl.cs +++ b/src/NHibernate/Async/Impl/AbstractSessionImpl.cs @@ -17,6 +17,7 @@ using NHibernate.AdoNet; using NHibernate.Cache; using NHibernate.Collection; +using NHibernate.Connection; using NHibernate.Engine; using NHibernate.Engine.Query; using NHibernate.Engine.Query.Sql; diff --git a/src/NHibernate/Impl/AbstractSessionImpl.cs b/src/NHibernate/Impl/AbstractSessionImpl.cs index 6255dc202f5..4df69524aa2 100644 --- a/src/NHibernate/Impl/AbstractSessionImpl.cs +++ b/src/NHibernate/Impl/AbstractSessionImpl.cs @@ -7,6 +7,7 @@ using NHibernate.AdoNet; using NHibernate.Cache; using NHibernate.Collection; +using NHibernate.Connection; using NHibernate.Engine; using NHibernate.Engine.Query; using NHibernate.Engine.Query.Sql; @@ -103,7 +104,7 @@ protected internal AbstractSessionImpl(ISessionFactoryImplementor factory, ISess options.SessionConnectionReleaseMode, Interceptor, _tenantConfiguration == null - ? new NonContextualConnectionAccess(_factory.ConnectionProvider) + ? new NonContextualConnectionAccess(_factory.ConnectionProvider.GetConnectionString()) : _factory.Settings.MultiTenancyConnectionProvider.GetConnectionAccess(_tenantConfiguration), options.ShouldAutoJoinTransaction); } diff --git a/src/NHibernate/Impl/NonContextualConnectionAccess.cs b/src/NHibernate/Impl/NonContextualConnectionAccess.cs index fe0536b2da9..7dcfb2b50f8 100644 --- a/src/NHibernate/Impl/NonContextualConnectionAccess.cs +++ b/src/NHibernate/Impl/NonContextualConnectionAccess.cs @@ -7,10 +7,9 @@ namespace NHibernate.Impl [Serializable] partial class NonContextualConnectionAccess : IConnectionAccess { - public NonContextualConnectionAccess(IConnectionProvider provider) + public NonContextualConnectionAccess(string connectionString) { - ConnectionString = provider.GetConnectionString(); - ; + ConnectionString = connectionString; } public DbConnection GetConnection(IConnectionProvider provider) From 839cfb51180f2ebea9cf8cac34cd47c83de9403d Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Mon, 22 Jun 2020 21:50:53 +0300 Subject: [PATCH 17/27] Commented --- src/NHibernate/Cfg/Environment.cs | 4 ++-- src/NHibernate/MultiTenancy/TenantConfiguration.cs | 2 +- src/NHibernate/nhibernate-configuration.xsd | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/NHibernate/Cfg/Environment.cs b/src/NHibernate/Cfg/Environment.cs index 2c48dbde3c0..537f193d126 100644 --- a/src/NHibernate/Cfg/Environment.cs +++ b/src/NHibernate/Cfg/Environment.cs @@ -392,9 +392,9 @@ public static string Version public const string MultiTenancy = "multiTenancy"; /// - /// Connection provider for given multi-tenancy strategy. + /// Connection provider for given multi-tenancy strategy. Class name implementing IMultiTenancyConnectionProvider. /// - public const string MultiTenancyConnectionProvider = "multiTenancy.connection.provider"; + public const string MultiTenancyConnectionProvider = "multiTenancy.connection_provider"; private static IBytecodeProvider BytecodeProviderInstance; private static bool EnableReflectionOptimizer; diff --git a/src/NHibernate/MultiTenancy/TenantConfiguration.cs b/src/NHibernate/MultiTenancy/TenantConfiguration.cs index 4f9001d565e..d6643522163 100644 --- a/src/NHibernate/MultiTenancy/TenantConfiguration.cs +++ b/src/NHibernate/MultiTenancy/TenantConfiguration.cs @@ -15,7 +15,7 @@ public class TenantConfiguration public string TenantIdentifier { get; set; } /// - /// Tenant Connection String. Usage depends on multi-tenancy connection provider "multiTenancy.connection.provider" + /// Tenant Connection String. Usage depends on multi-tenancy connection provider "multiTenancy.connection_provider" /// public string ConnectionString { get; set; } diff --git a/src/NHibernate/nhibernate-configuration.xsd b/src/NHibernate/nhibernate-configuration.xsd index fc8f753b1a4..db53fce14c8 100644 --- a/src/NHibernate/nhibernate-configuration.xsd +++ b/src/NHibernate/nhibernate-configuration.xsd @@ -339,14 +339,14 @@ - Strategy for multi-tenancy. See MultiTenancyStrategy enum for supported values + Strategy for multi-tenancy. Supported Values: Database, None. Corresponds to MultiTenancyStrategy enum. - + - Connection provider for given multi-tenancy strategy. + Connection provider for given multi-tenancy strategy. Class name implementing IMultiTenancyConnectionProvider. From 4fcd32faef7ef616e562e1bd1aa95d1c454af9d8 Mon Sep 17 00:00:00 2001 From: maca88 Date: Thu, 25 Jun 2020 22:31:09 +0200 Subject: [PATCH 18/27] Code refactoring --- .../DatabaseStrategyNoDbSpecificFixture.cs | 8 +-- .../DatabaseStrategyNoDbSpecificFixture.cs | 16 ++--- .../TestMultiTenancyConnectionProvider.cs | 12 ++-- src/NHibernate/AdoNet/ConnectionManager.cs | 6 +- .../Async/AdoNet/ConnectionManager.cs | 2 +- .../Async/Connection/IConnectionAccess.cs | 8 ++- .../Impl/NonContextualConnectionAccess.cs | 6 +- ...AbstractMultiTenancyConnectionProvider.cs} | 9 +-- src/NHibernate/Cfg/Environment.cs | 6 +- src/NHibernate/Cfg/SettingsFactory.cs | 5 +- .../Connection/IConnectionAccess.cs | 19 ++++-- src/NHibernate/Impl/AbstractSessionImpl.cs | 12 +++- .../Impl/NonContextualConnectionAccess.cs | 25 +++++--- .../AbstractMultiTenancyConnectionProvider.cs | 61 +++++++++++++++++++ .../DefaultMultiTenancyConnectionProvider.cs | 55 ----------------- .../IMultiTenancyConnectionProvider.cs | 8 ++- .../MultiTenancy/TenantConfiguration.cs | 9 +-- src/NHibernate/nhibernate-configuration.xsd | 4 +- 18 files changed, 150 insertions(+), 121 deletions(-) rename src/NHibernate/Async/MultiTenancy/{DefaultMultiTenancyConnectionProvider.cs => AbstractMultiTenancyConnectionProvider.cs} (68%) create mode 100644 src/NHibernate/MultiTenancy/AbstractMultiTenancyConnectionProvider.cs delete mode 100644 src/NHibernate/MultiTenancy/DefaultMultiTenancyConnectionProvider.cs diff --git a/src/NHibernate.Test/Async/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs b/src/NHibernate.Test/Async/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs index 59db07753a0..1826faad731 100644 --- a/src/NHibernate.Test/Async/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs +++ b/src/NHibernate.Test/Async/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs @@ -16,7 +16,6 @@ using System.Runtime.Serialization.Formatters.Binary; using NHibernate.Cfg; using NHibernate.Cfg.MappingSchema; -using NHibernate.Connection; using NHibernate.Dialect; using NHibernate.Driver; using NHibernate.Engine; @@ -199,10 +198,7 @@ private IStatelessSession OpenTenantStatelessSession(string tenantId) private TenantConfiguration GetTenantConfig(string tenantId) { - return new TestTenantConfiguration(tenantId, IsSqlServerDialect) - { - ConnectionString = Sfi.ConnectionProvider.GetConnectionString() - }; + return new TestTenantConfiguration(tenantId, IsSqlServerDialect); } private bool IsSqlServerDialect => Sfi.Dialect is MsSql2000Dialect && !(Sfi.ConnectionProvider.Driver is OdbcDriver); @@ -227,7 +223,7 @@ protected override HbmMapping GetMappings() protected override DbConnection OpenConnectionForSchemaExport() { return Sfi.Settings.MultiTenancyConnectionProvider - .GetConnectionAccess(GetTenantConfig("defaultTenant")).GetConnection(Sfi.ConnectionProvider); + .GetConnectionAccess(GetTenantConfig("defaultTenant"), Sfi).GetConnection(); } protected override ISession OpenSession() diff --git a/src/NHibernate.Test/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs b/src/NHibernate.Test/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs index 5d2f0d69d81..e3f2ad08e2c 100644 --- a/src/NHibernate.Test/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs +++ b/src/NHibernate.Test/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs @@ -6,7 +6,6 @@ using System.Runtime.Serialization.Formatters.Binary; using NHibernate.Cfg; using NHibernate.Cfg.MappingSchema; -using NHibernate.Connection; using NHibernate.Dialect; using NHibernate.Driver; using NHibernate.Engine; @@ -38,9 +37,7 @@ protected override void Configure(Configuration configuration) [Test] public void ShouldThrowWithNoTenantIdentifier() { - var sessionBuilder = Sfi.WithOptions().TenantConfiguration(new TenantConfiguration(null)); - - Assert.That(() => sessionBuilder.OpenSession(), Throws.ArgumentException); + Assert.Throws(() => Sfi.WithOptions().TenantConfiguration(new TenantConfiguration(null))); } [Test] @@ -63,9 +60,7 @@ public void DifferentConnectionStringForDifferentTenants() [Test] public void StatelessSessionShouldThrowWithNoTenantIdentifier() { - var sessionBuilder = Sfi.WithStatelessOptions().TenantConfiguration(new TenantConfiguration(null)); - - Assert.That(() => sessionBuilder.OpenStatelessSession(), Throws.ArgumentException); + Assert.Throws(() => Sfi.WithStatelessOptions().TenantConfiguration(new TenantConfiguration(null))); } [Test] @@ -272,10 +267,7 @@ private IStatelessSession OpenTenantStatelessSession(string tenantId) private TenantConfiguration GetTenantConfig(string tenantId) { - return new TestTenantConfiguration(tenantId, IsSqlServerDialect) - { - ConnectionString = Sfi.ConnectionProvider.GetConnectionString() - }; + return new TestTenantConfiguration(tenantId, IsSqlServerDialect); } private bool IsSqlServerDialect => Sfi.Dialect is MsSql2000Dialect && !(Sfi.ConnectionProvider.Driver is OdbcDriver); @@ -300,7 +292,7 @@ protected override HbmMapping GetMappings() protected override DbConnection OpenConnectionForSchemaExport() { return Sfi.Settings.MultiTenancyConnectionProvider - .GetConnectionAccess(GetTenantConfig("defaultTenant")).GetConnection(Sfi.ConnectionProvider); + .GetConnectionAccess(GetTenantConfig("defaultTenant"), Sfi).GetConnection(); } protected override ISession OpenSession() diff --git a/src/NHibernate.Test/MultiTenancy/TestMultiTenancyConnectionProvider.cs b/src/NHibernate.Test/MultiTenancy/TestMultiTenancyConnectionProvider.cs index ee6dc054cc4..b690b78f14c 100644 --- a/src/NHibernate.Test/MultiTenancy/TestMultiTenancyConnectionProvider.cs +++ b/src/NHibernate.Test/MultiTenancy/TestMultiTenancyConnectionProvider.cs @@ -1,17 +1,19 @@ using System; using System.Data.SqlClient; +using NHibernate.Connection; +using NHibernate.Engine; using NHibernate.MultiTenancy; namespace NHibernate.Test.MultiTenancy { [Serializable] - public class TestMultiTenancyConnectionProvider : DefaultMultiTenancyConnectionProvider + public class TestMultiTenancyConnectionProvider : AbstractMultiTenancyConnectionProvider { - protected override string GetTenantConnectionString(TenantConfiguration configuration) + protected override string GetTenantConnectionString(TenantConfiguration tenantConfiguration, ISessionFactoryImplementor sessionFactory) { - return configuration is TestTenantConfiguration tenant && tenant.IsSqlServerDialect - ? new SqlConnectionStringBuilder(configuration.ConnectionString) {ApplicationName = configuration.TenantIdentifier}.ToString() - : base.GetTenantConnectionString(configuration); + return tenantConfiguration is TestTenantConfiguration tenant && tenant.IsSqlServerDialect + ? new SqlConnectionStringBuilder(sessionFactory.ConnectionProvider.GetConnectionString()) {ApplicationName = tenantConfiguration.TenantIdentifier}.ToString() + : sessionFactory.ConnectionProvider.GetConnectionString(); } } } diff --git a/src/NHibernate/AdoNet/ConnectionManager.cs b/src/NHibernate/AdoNet/ConnectionManager.cs index 00912cb81c4..8c5d1fa3aa9 100644 --- a/src/NHibernate/AdoNet/ConnectionManager.cs +++ b/src/NHibernate/AdoNet/ConnectionManager.cs @@ -165,7 +165,7 @@ public DbConnection Close() if (_backupConnection != null) { _log.Warn("Backup connection was still defined at time of closing."); - _connectionAccess.CloseConnection(_backupConnection, Factory.ConnectionProvider); + _connectionAccess.CloseConnection(_backupConnection); _backupConnection = null; } @@ -222,7 +222,7 @@ public DbConnection Disconnect() private void CloseConnection() { - _connectionAccess.CloseConnection(_connection, Factory.ConnectionProvider); + _connectionAccess.CloseConnection(_connection); _connection = null; } @@ -256,7 +256,7 @@ public DbConnection GetConnection() { if (_ownConnection) { - _connection = _connectionAccess.GetConnection(Factory.ConnectionProvider); + _connection = _connectionAccess.GetConnection(); // 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 670d4c59faf..a716845c6e9 100644 --- a/src/NHibernate/Async/AdoNet/ConnectionManager.cs +++ b/src/NHibernate/Async/AdoNet/ConnectionManager.cs @@ -61,7 +61,7 @@ async Task InternalGetConnectionAsync() { if (_ownConnection) { - _connection = await (_connectionAccess.GetConnectionAsync(Factory.ConnectionProvider, cancellationToken)).ConfigureAwait(false); + _connection = await (_connectionAccess.GetConnectionAsync(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/Connection/IConnectionAccess.cs b/src/NHibernate/Async/Connection/IConnectionAccess.cs index 2fea116ad20..eb8c1ebd506 100644 --- a/src/NHibernate/Async/Connection/IConnectionAccess.cs +++ b/src/NHibernate/Async/Connection/IConnectionAccess.cs @@ -16,7 +16,13 @@ namespace NHibernate.Connection using System.Threading; public partial interface IConnectionAccess { + //ObtainConnection in hibernate - Task GetConnectionAsync(IConnectionProvider provider, CancellationToken cancellationToken); + /// + /// Gets the database connection. + /// + /// A cancellation token that can be used to cancel the work + /// The database connection. + Task GetConnectionAsync(CancellationToken cancellationToken); } } diff --git a/src/NHibernate/Async/Impl/NonContextualConnectionAccess.cs b/src/NHibernate/Async/Impl/NonContextualConnectionAccess.cs index 7078ad828f5..99e8efa7c31 100644 --- a/src/NHibernate/Async/Impl/NonContextualConnectionAccess.cs +++ b/src/NHibernate/Async/Impl/NonContextualConnectionAccess.cs @@ -11,6 +11,7 @@ using System; using System.Data.Common; using NHibernate.Connection; +using NHibernate.Engine; namespace NHibernate.Impl { @@ -19,13 +20,14 @@ namespace NHibernate.Impl partial class NonContextualConnectionAccess : IConnectionAccess { - public Task GetConnectionAsync(IConnectionProvider provider, CancellationToken cancellationToken) + /// + public Task GetConnectionAsync(CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) { return Task.FromCanceled(cancellationToken); } - return provider.GetConnectionAsync(ConnectionString, cancellationToken); + return _sessionFactory.ConnectionProvider.GetConnectionAsync(cancellationToken); } } } diff --git a/src/NHibernate/Async/MultiTenancy/DefaultMultiTenancyConnectionProvider.cs b/src/NHibernate/Async/MultiTenancy/AbstractMultiTenancyConnectionProvider.cs similarity index 68% rename from src/NHibernate/Async/MultiTenancy/DefaultMultiTenancyConnectionProvider.cs rename to src/NHibernate/Async/MultiTenancy/AbstractMultiTenancyConnectionProvider.cs index 7483d0a6bc2..123bcad9827 100644 --- a/src/NHibernate/Async/MultiTenancy/DefaultMultiTenancyConnectionProvider.cs +++ b/src/NHibernate/Async/MultiTenancy/AbstractMultiTenancyConnectionProvider.cs @@ -11,24 +11,25 @@ using System; using System.Data.Common; using NHibernate.Connection; -using Environment = NHibernate.Cfg.Environment; +using NHibernate.Engine; namespace NHibernate.MultiTenancy { using System.Threading.Tasks; using System.Threading; - public partial class DefaultMultiTenancyConnectionProvider : IMultiTenancyConnectionProvider + public abstract partial class AbstractMultiTenancyConnectionProvider : IMultiTenancyConnectionProvider { partial class ContextualConnectionAccess : IConnectionAccess { - public Task GetConnectionAsync(IConnectionProvider provider, CancellationToken cancellationToken) + /// + public Task GetConnectionAsync(CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) { return Task.FromCanceled(cancellationToken); } - return provider.GetConnectionAsync(ConnectionString, cancellationToken); + return _sessionFactory.ConnectionProvider.GetConnectionAsync(ConnectionString, cancellationToken); } } } diff --git a/src/NHibernate/Cfg/Environment.cs b/src/NHibernate/Cfg/Environment.cs index 537f193d126..bce4db95f0c 100644 --- a/src/NHibernate/Cfg/Environment.cs +++ b/src/NHibernate/Cfg/Environment.cs @@ -387,14 +387,14 @@ public static string Version private static readonly Dictionary GlobalProperties = new Dictionary(); /// - /// Strategy for multi-tenancy. + /// Strategy for multi-tenancy. /// - public const string MultiTenancy = "multiTenancy"; + public const string MultiTenancy = "multi_tenancy.strategy"; /// /// Connection provider for given multi-tenancy strategy. Class name implementing IMultiTenancyConnectionProvider. /// - public const string MultiTenancyConnectionProvider = "multiTenancy.connection_provider"; + public const string MultiTenancyConnectionProvider = "multi_tenancy.connection_provider"; private static IBytecodeProvider BytecodeProviderInstance; private static bool EnableReflectionOptimizer; diff --git a/src/NHibernate/Cfg/SettingsFactory.cs b/src/NHibernate/Cfg/SettingsFactory.cs index 84ae6c48194..f1f1a0ffb29 100644 --- a/src/NHibernate/Cfg/SettingsFactory.cs +++ b/src/NHibernate/Cfg/SettingsFactory.cs @@ -426,13 +426,12 @@ private static IMultiTenancyConnectionProvider CreateMultiTenancyConnectionProvi Environment.MultiTenancyConnectionProvider, properties, null); + log.Info("Multi-tenancy connection provider: {0}", className); if (className == null) { - log.Info("Default Multi-tenancy connection provider is used: {0}", typeof(DefaultMultiTenancyConnectionProvider).FullName); - return new DefaultMultiTenancyConnectionProvider(); + return null; } - log.Info("Multi-tenancy connection provider: {0}", className); try { return (IMultiTenancyConnectionProvider) diff --git a/src/NHibernate/Connection/IConnectionAccess.cs b/src/NHibernate/Connection/IConnectionAccess.cs index fd683aea519..d76f6f5194f 100644 --- a/src/NHibernate/Connection/IConnectionAccess.cs +++ b/src/NHibernate/Connection/IConnectionAccess.cs @@ -10,12 +10,23 @@ namespace NHibernate.Connection /// public partial interface IConnectionAccess { + /// + /// The connection string of the database connection. + /// + string ConnectionString { get; } + //ObtainConnection in hibernate - DbConnection GetConnection(IConnectionProvider provider); + /// + /// Gets the database connection. + /// + /// The database connection. + DbConnection GetConnection(); //ReleaseConnection in hibernate - void CloseConnection(DbConnection conn, IConnectionProvider provider); - - string ConnectionString { get; } + /// + /// Closes the given database connection. + /// + /// The connection to close. + void CloseConnection(DbConnection connection); } } diff --git a/src/NHibernate/Impl/AbstractSessionImpl.cs b/src/NHibernate/Impl/AbstractSessionImpl.cs index 4df69524aa2..cc3c864d8c0 100644 --- a/src/NHibernate/Impl/AbstractSessionImpl.cs +++ b/src/NHibernate/Impl/AbstractSessionImpl.cs @@ -68,7 +68,13 @@ private TenantConfiguration ValidateTenantConfiguration(ISessionFactoryImplement if (string.IsNullOrEmpty(tenantConfiguration?.TenantIdentifier)) { - throw new ArgumentException("Tenant configuration with TenantIdentifier defined is required for multi-tenancy.", nameof(tenantConfiguration)); + throw new ArgumentException("Tenant configuration with TenantIdentifier defined is required for multi-tenancy.", nameof(tenantConfiguration)); + } + + if (_factory.Settings.MultiTenancyConnectionProvider == null) + { + throw new ArgumentException( + $"IMultiTenantConnectionProvider is required for multi-tenancy. Provide it via '{Cfg.Environment.MultiTenancyConnectionProvider}` session factory setting."); } return tenantConfiguration; @@ -104,8 +110,8 @@ protected internal AbstractSessionImpl(ISessionFactoryImplementor factory, ISess options.SessionConnectionReleaseMode, Interceptor, _tenantConfiguration == null - ? new NonContextualConnectionAccess(_factory.ConnectionProvider.GetConnectionString()) - : _factory.Settings.MultiTenancyConnectionProvider.GetConnectionAccess(_tenantConfiguration), + ? new NonContextualConnectionAccess(_factory) + : _factory.Settings.MultiTenancyConnectionProvider.GetConnectionAccess(_tenantConfiguration, _factory), options.ShouldAutoJoinTransaction); } } diff --git a/src/NHibernate/Impl/NonContextualConnectionAccess.cs b/src/NHibernate/Impl/NonContextualConnectionAccess.cs index 7dcfb2b50f8..56dc831935c 100644 --- a/src/NHibernate/Impl/NonContextualConnectionAccess.cs +++ b/src/NHibernate/Impl/NonContextualConnectionAccess.cs @@ -1,27 +1,36 @@ using System; using System.Data.Common; using NHibernate.Connection; +using NHibernate.Engine; namespace NHibernate.Impl { + /// + /// A non contextual connection access used when multi-tenant is not enabled. + /// [Serializable] partial class NonContextualConnectionAccess : IConnectionAccess { - public NonContextualConnectionAccess(string connectionString) + private readonly ISessionFactoryImplementor _sessionFactory; + + public NonContextualConnectionAccess(ISessionFactoryImplementor connectionProvider) { - ConnectionString = connectionString; + _sessionFactory = connectionProvider; } - public DbConnection GetConnection(IConnectionProvider provider) + /// + public string ConnectionString => _sessionFactory.ConnectionProvider.GetConnectionString(); + + /// + public DbConnection GetConnection() { - return provider.GetConnection(ConnectionString); + return _sessionFactory.ConnectionProvider.GetConnection(); } - public void CloseConnection(DbConnection conn, IConnectionProvider provider) + /// + public void CloseConnection(DbConnection connection) { - provider.CloseConnection(conn); + _sessionFactory.ConnectionProvider.CloseConnection(connection); } - - public string ConnectionString { get; } } } diff --git a/src/NHibernate/MultiTenancy/AbstractMultiTenancyConnectionProvider.cs b/src/NHibernate/MultiTenancy/AbstractMultiTenancyConnectionProvider.cs new file mode 100644 index 00000000000..1c19f8c0018 --- /dev/null +++ b/src/NHibernate/MultiTenancy/AbstractMultiTenancyConnectionProvider.cs @@ -0,0 +1,61 @@ +using System; +using System.Data.Common; +using NHibernate.Connection; +using NHibernate.Engine; + +namespace NHibernate.MultiTenancy +{ + /// + /// Base implementation for multi-tenancy strategy. + /// + [Serializable] + public abstract partial class AbstractMultiTenancyConnectionProvider : IMultiTenancyConnectionProvider + { + /// + public IConnectionAccess GetConnectionAccess(TenantConfiguration tenantConfiguration, ISessionFactoryImplementor sessionFactory) + { + var tenantConnectionString = GetTenantConnectionString(tenantConfiguration, sessionFactory); + if (string.IsNullOrEmpty(tenantConnectionString)) + { + throw new HibernateException($"Tenant '{tenantConfiguration.TenantIdentifier}' connection string is empty."); + } + + return new ContextualConnectionAccess(tenantConnectionString, sessionFactory); + } + + /// + /// Gets the connection string for the given tenant configuration. + /// + /// The tenant configuration. + /// The session factory. + /// The connection string for the tenant. + protected abstract string GetTenantConnectionString(TenantConfiguration tenantConfiguration, ISessionFactoryImplementor sessionFactory); + + [Serializable] + partial class ContextualConnectionAccess : IConnectionAccess + { + private readonly ISessionFactoryImplementor _sessionFactory; + + public ContextualConnectionAccess(string connectionString, ISessionFactoryImplementor sessionFactory) + { + ConnectionString = connectionString; + _sessionFactory = sessionFactory; + } + + /// + public string ConnectionString { get; } + + /// + public DbConnection GetConnection() + { + return _sessionFactory.ConnectionProvider.GetConnection(ConnectionString); + } + + /// + public void CloseConnection(DbConnection connection) + { + _sessionFactory.ConnectionProvider.CloseConnection(connection); + } + } + } +} diff --git a/src/NHibernate/MultiTenancy/DefaultMultiTenancyConnectionProvider.cs b/src/NHibernate/MultiTenancy/DefaultMultiTenancyConnectionProvider.cs deleted file mode 100644 index f9bfbb3f0ac..00000000000 --- a/src/NHibernate/MultiTenancy/DefaultMultiTenancyConnectionProvider.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System; -using System.Data.Common; -using NHibernate.Connection; -using Environment = NHibernate.Cfg.Environment; - -namespace NHibernate.MultiTenancy -{ - /// - /// Base implementation for multi-tenancy strategy - /// - [Serializable] - public partial class DefaultMultiTenancyConnectionProvider : IMultiTenancyConnectionProvider - { - /// - public IConnectionAccess GetConnectionAccess(TenantConfiguration configuration) - { - var tenantConnectionString = GetTenantConnectionString(configuration); - if (string.IsNullOrEmpty(tenantConnectionString)) - { - throw new HibernateException( - message: $"Tenant '{configuration.TenantIdentifier}' connection string is empty." + - $" Either provide it on each session opening in TenantConfiguration.ConnectionString or" + - $" provide custom IMultiTenantConnectionProvider via '{Environment.MultiTenancyConnectionProvider}` session factory setting."); - } - - return new ContextualConnectionAccess(tenantConnectionString); - } - - protected virtual string GetTenantConnectionString(TenantConfiguration configuration) - { - return configuration.ConnectionString; - } - - [Serializable] - partial class ContextualConnectionAccess : IConnectionAccess - { - public ContextualConnectionAccess(string connectionString) - { - ConnectionString = connectionString; - } - - public DbConnection GetConnection(IConnectionProvider provider) - { - return provider.GetConnection(ConnectionString); - } - - public void CloseConnection(DbConnection conn, IConnectionProvider provider) - { - provider.CloseConnection(conn); - } - - public string ConnectionString { get; } - } - } -} diff --git a/src/NHibernate/MultiTenancy/IMultiTenancyConnectionProvider.cs b/src/NHibernate/MultiTenancy/IMultiTenancyConnectionProvider.cs index d875034b444..045e8f0fdc0 100644 --- a/src/NHibernate/MultiTenancy/IMultiTenancyConnectionProvider.cs +++ b/src/NHibernate/MultiTenancy/IMultiTenancyConnectionProvider.cs @@ -1,4 +1,5 @@ using NHibernate.Connection; +using NHibernate.Engine; namespace NHibernate.MultiTenancy { @@ -9,8 +10,11 @@ namespace NHibernate.MultiTenancy public interface IMultiTenancyConnectionProvider { /// - /// Tenant connection access + /// Gets the tenant connection access. /// - IConnectionAccess GetConnectionAccess(TenantConfiguration configuration); + /// The tenant configuration. + /// The session factory. + /// The tenant connection access. + IConnectionAccess GetConnectionAccess(TenantConfiguration tenantConfiguration, ISessionFactoryImplementor sessionFactory); } } diff --git a/src/NHibernate/MultiTenancy/TenantConfiguration.cs b/src/NHibernate/MultiTenancy/TenantConfiguration.cs index d6643522163..9343b9da009 100644 --- a/src/NHibernate/MultiTenancy/TenantConfiguration.cs +++ b/src/NHibernate/MultiTenancy/TenantConfiguration.cs @@ -12,16 +12,11 @@ public class TenantConfiguration /// Tenant identifier must uniquely identify tenant /// Note: Among other things this value is used for data separation between tenants in cache so not unique value will leak data to other tenants /// - public string TenantIdentifier { get; set; } - - /// - /// Tenant Connection String. Usage depends on multi-tenancy connection provider "multiTenancy.connection_provider" - /// - public string ConnectionString { get; set; } + public string TenantIdentifier { get; } public TenantConfiguration(string tenantIdentifier) { - TenantIdentifier = tenantIdentifier; + TenantIdentifier = tenantIdentifier ?? throw new ArgumentNullException(nameof(tenantIdentifier)); } } } diff --git a/src/NHibernate/nhibernate-configuration.xsd b/src/NHibernate/nhibernate-configuration.xsd index db53fce14c8..178be1bfe37 100644 --- a/src/NHibernate/nhibernate-configuration.xsd +++ b/src/NHibernate/nhibernate-configuration.xsd @@ -336,14 +336,14 @@ - + Strategy for multi-tenancy. Supported Values: Database, None. Corresponds to MultiTenancyStrategy enum. - + Connection provider for given multi-tenancy strategy. Class name implementing IMultiTenancyConnectionProvider. From d9227cd70a55cc2c03689e9c8b1025f53ae8e126 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Fri, 26 Jun 2020 12:21:27 +0300 Subject: [PATCH 19/27] Commented --- .../DatabaseStrategyNoDbSpecificFixture.cs | 4 ++-- src/NHibernate.Test/DebugSessionFactory.cs | 4 ++-- .../DatabaseStrategyNoDbSpecificFixture.cs | 8 ++++---- src/NHibernate/Cfg/Environment.cs | 2 +- src/NHibernate/ISessionBuilder.cs | 17 +++++++++++++---- src/NHibernate/IStatelessSessionBuilder.cs | 17 +++++++++++++---- src/NHibernate/Impl/AbstractSessionImpl.cs | 8 ++++++-- .../Impl/NonContextualConnectionAccess.cs | 2 +- .../MultiTenancy/TenantConfiguration.cs | 3 ++- 9 files changed, 44 insertions(+), 21 deletions(-) diff --git a/src/NHibernate.Test/Async/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs b/src/NHibernate.Test/Async/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs index 1826faad731..3fc06fb5a44 100644 --- a/src/NHibernate.Test/Async/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs +++ b/src/NHibernate.Test/Async/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs @@ -188,12 +188,12 @@ private T SpoofSerialization(T session) private ISession OpenTenantSession(string tenantId) { - return Sfi.WithOptions().TenantConfiguration(GetTenantConfig(tenantId)).OpenSession(); + return Sfi.WithOptions().Tenant(GetTenantConfig(tenantId)).OpenSession(); } private IStatelessSession OpenTenantStatelessSession(string tenantId) { - return Sfi.WithStatelessOptions().TenantConfiguration(GetTenantConfig(tenantId)).OpenStatelessSession(); + return Sfi.WithStatelessOptions().Tenant(GetTenantConfig(tenantId)).OpenStatelessSession(); } private TenantConfiguration GetTenantConfig(string tenantId) diff --git a/src/NHibernate.Test/DebugSessionFactory.cs b/src/NHibernate.Test/DebugSessionFactory.cs index 037cb56b795..7643924119f 100644 --- a/src/NHibernate.Test/DebugSessionFactory.cs +++ b/src/NHibernate.Test/DebugSessionFactory.cs @@ -472,7 +472,7 @@ ISessionBuilder ISessionBuilder.FlushMode(FlushMode flushMode) TenantConfiguration ISessionCreationOptionsWithMultiTenancy.TenantConfiguration { get => (_actualBuilder as ISessionCreationOptionsWithMultiTenancy)?.TenantConfiguration; - set => _actualBuilder.TenantConfiguration(value); + set => _actualBuilder.Tenant(value); } } @@ -515,7 +515,7 @@ IStatelessSessionBuilder IStatelessSessionBuilder.AutoJoinTransaction(bool autoJ TenantConfiguration ISessionCreationOptionsWithMultiTenancy.TenantConfiguration { get => (_actualBuilder as ISessionCreationOptionsWithMultiTenancy)?.TenantConfiguration; - set => _actualBuilder.TenantConfiguration(value); + set => _actualBuilder.Tenant(value); } #endregion diff --git a/src/NHibernate.Test/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs b/src/NHibernate.Test/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs index e3f2ad08e2c..2a37052266b 100644 --- a/src/NHibernate.Test/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs +++ b/src/NHibernate.Test/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs @@ -37,7 +37,7 @@ protected override void Configure(Configuration configuration) [Test] public void ShouldThrowWithNoTenantIdentifier() { - Assert.Throws(() => Sfi.WithOptions().TenantConfiguration(new TenantConfiguration(null))); + Assert.Throws(() => Sfi.WithOptions().Tenant(new TenantConfiguration(null))); } [Test] @@ -60,7 +60,7 @@ public void DifferentConnectionStringForDifferentTenants() [Test] public void StatelessSessionShouldThrowWithNoTenantIdentifier() { - Assert.Throws(() => Sfi.WithStatelessOptions().TenantConfiguration(new TenantConfiguration(null))); + Assert.Throws(() => Sfi.WithStatelessOptions().Tenant(new TenantConfiguration(null))); } [Test] @@ -257,12 +257,12 @@ private T SpoofSerialization(T session) private ISession OpenTenantSession(string tenantId) { - return Sfi.WithOptions().TenantConfiguration(GetTenantConfig(tenantId)).OpenSession(); + return Sfi.WithOptions().Tenant(GetTenantConfig(tenantId)).OpenSession(); } private IStatelessSession OpenTenantStatelessSession(string tenantId) { - return Sfi.WithStatelessOptions().TenantConfiguration(GetTenantConfig(tenantId)).OpenStatelessSession(); + return Sfi.WithStatelessOptions().Tenant(GetTenantConfig(tenantId)).OpenStatelessSession(); } private TenantConfiguration GetTenantConfig(string tenantId) diff --git a/src/NHibernate/Cfg/Environment.cs b/src/NHibernate/Cfg/Environment.cs index bce4db95f0c..973657f803d 100644 --- a/src/NHibernate/Cfg/Environment.cs +++ b/src/NHibernate/Cfg/Environment.cs @@ -388,7 +388,7 @@ public static string Version /// /// Strategy for multi-tenancy. - /// + /// See also public const string MultiTenancy = "multi_tenancy.strategy"; /// diff --git a/src/NHibernate/ISessionBuilder.cs b/src/NHibernate/ISessionBuilder.cs index 080e5f0c070..b1c5c7b4de3 100644 --- a/src/NHibernate/ISessionBuilder.cs +++ b/src/NHibernate/ISessionBuilder.cs @@ -6,14 +6,23 @@ namespace NHibernate { - //TODO 6.0: Merge into ISessionBuilder public static class SessionBuilderExtensions { /// - /// Provides tenant configuration required for multi-tenancy - /// + /// Associates session with given tenantIdentifier when multi-tenancy is enabled. + /// See /// - public static T TenantConfiguration(this T builder, TenantConfiguration tenantConfig) where T: ISessionBuilder + public static T Tenant(this T builder, string tenantIdentifier) where T : ISessionBuilder + { + return builder.Tenant(new TenantConfiguration(tenantIdentifier)); + } + + //TODO 6.0: Merge into ISessionBuilder + /// + /// Associates session with given tenantConfig when multi-tenancy is enabled. + /// See + /// + public static T Tenant(this T builder, TenantConfiguration tenantConfig) where T: ISessionBuilder { ReflectHelper.CastOrThrow(builder, "multi tenancy").TenantConfiguration = tenantConfig; return builder; diff --git a/src/NHibernate/IStatelessSessionBuilder.cs b/src/NHibernate/IStatelessSessionBuilder.cs index 2dc3891a36d..a8d2ecfcac7 100644 --- a/src/NHibernate/IStatelessSessionBuilder.cs +++ b/src/NHibernate/IStatelessSessionBuilder.cs @@ -6,14 +6,23 @@ namespace NHibernate { - //TODO 6.0: Merge into IStatelessSessionBuilder public static class StatelessSessionBuilderExtensions { /// - /// Provides tenant configuration required for multi-tenancy - /// + /// Associates stateless session with given tenantIdentifier when multi-tenancy is enabled. + /// See /// - public static IStatelessSessionBuilder TenantConfiguration(this IStatelessSessionBuilder builder, TenantConfiguration tenantConfig) + public static T Tenant(this T builder, string tenantIdentifier) where T : ISessionBuilder + { + return builder.Tenant(new TenantConfiguration(tenantIdentifier)); + } + + //TODO 6.0: Merge into IStatelessSessionBuilder + /// + /// Associates stateless session with given tenantConfig when multi-tenancy is enabled. + /// See + /// + public static IStatelessSessionBuilder Tenant(this IStatelessSessionBuilder builder, TenantConfiguration tenantConfig) { ReflectHelper.CastOrThrow(builder, "multi tenancy").TenantConfiguration = tenantConfig; return builder; diff --git a/src/NHibernate/Impl/AbstractSessionImpl.cs b/src/NHibernate/Impl/AbstractSessionImpl.cs index cc3c864d8c0..c3b5eeacc8a 100644 --- a/src/NHibernate/Impl/AbstractSessionImpl.cs +++ b/src/NHibernate/Impl/AbstractSessionImpl.cs @@ -68,13 +68,17 @@ private TenantConfiguration ValidateTenantConfiguration(ISessionFactoryImplement if (string.IsNullOrEmpty(tenantConfiguration?.TenantIdentifier)) { - throw new ArgumentException("Tenant configuration with TenantIdentifier defined is required for multi-tenancy.", nameof(tenantConfiguration)); + throw new ArgumentException( + $"Tenant configuration with `{nameof(TenantConfiguration.TenantIdentifier)}` defined is required for multi-tenancy.", + nameof(tenantConfiguration)); } if (_factory.Settings.MultiTenancyConnectionProvider == null) { throw new ArgumentException( - $"IMultiTenantConnectionProvider is required for multi-tenancy. Provide it via '{Cfg.Environment.MultiTenancyConnectionProvider}` session factory setting."); + $"`{nameof(IMultiTenancyConnectionProvider)}` is required for multi-tenancy." + + $" Provide it via '{Cfg.Environment.MultiTenancyConnectionProvider}` session factory setting." + + $" You can use `{nameof(AbstractMultiTenancyConnectionProvider)}` as a base."); } return tenantConfiguration; diff --git a/src/NHibernate/Impl/NonContextualConnectionAccess.cs b/src/NHibernate/Impl/NonContextualConnectionAccess.cs index 56dc831935c..2f3e2670de9 100644 --- a/src/NHibernate/Impl/NonContextualConnectionAccess.cs +++ b/src/NHibernate/Impl/NonContextualConnectionAccess.cs @@ -6,7 +6,7 @@ namespace NHibernate.Impl { /// - /// A non contextual connection access used when multi-tenant is not enabled. + /// A non contextual connection access used when multi-tenancy is not enabled. /// [Serializable] partial class NonContextualConnectionAccess : IConnectionAccess diff --git a/src/NHibernate/MultiTenancy/TenantConfiguration.cs b/src/NHibernate/MultiTenancy/TenantConfiguration.cs index 9343b9da009..b7fee880437 100644 --- a/src/NHibernate/MultiTenancy/TenantConfiguration.cs +++ b/src/NHibernate/MultiTenancy/TenantConfiguration.cs @@ -3,7 +3,8 @@ namespace NHibernate.MultiTenancy { /// - /// Tenant specific configuration + /// Tenant specific configuration. + /// This class can be used as base class for user complex tenant configurations. /// [Serializable] public class TenantConfiguration From 1371a73230072da80a8d9d7c98023d8f5bab578e Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Fri, 26 Jun 2020 12:30:15 +0300 Subject: [PATCH 20/27] Commented --- src/NHibernate/Connection/IConnectionProvider.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/NHibernate/Connection/IConnectionProvider.cs b/src/NHibernate/Connection/IConnectionProvider.cs index e1238a72d79..d40943915ea 100644 --- a/src/NHibernate/Connection/IConnectionProvider.cs +++ b/src/NHibernate/Connection/IConnectionProvider.cs @@ -6,7 +6,7 @@ namespace NHibernate.Connection { - //TODO: Merge into IConnectionProvider + //6.0 TODO: Merge into IConnectionProvider public static partial class ConnectionProviderExtensions { internal static DbConnection GetConnection(this IConnectionProvider connectionProvider, string connectionString) @@ -14,7 +14,7 @@ internal static DbConnection GetConnection(this IConnectionProvider connectionPr return ReflectHelper.CastOrThrow(connectionProvider, "open connection by connectionString").GetConnection(connectionString); } - //TODO: Expose as ConnectionString property + //6.0 TODO: Expose as ConnectionString property public static string GetConnectionString(this IConnectionProvider connectionProvider) { return ReflectHelper.CastOrThrow(connectionProvider, "retrieve connectionString").ConnectionString; From fa3eddd88cbdd68271aaae103bdf0519cb27ba91 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Fri, 26 Jun 2020 12:56:06 +0300 Subject: [PATCH 21/27] Avoid breaking change --- src/NHibernate/AdoNet/ConnectionManager.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/NHibernate/AdoNet/ConnectionManager.cs b/src/NHibernate/AdoNet/ConnectionManager.cs index 8c5d1fa3aa9..37fd4882c67 100644 --- a/src/NHibernate/AdoNet/ConnectionManager.cs +++ b/src/NHibernate/AdoNet/ConnectionManager.cs @@ -6,6 +6,7 @@ using System.Security; using NHibernate.Connection; using NHibernate.Engine; +using NHibernate.Impl; namespace NHibernate.AdoNet { @@ -81,10 +82,16 @@ public ConnectionManager( IInterceptor interceptor, IConnectionAccess connectionAccess, bool shouldAutoJoinTransaction) -#pragma warning disable 618 - : this(session, suppliedConnection, connectionReleaseMode, interceptor, shouldAutoJoinTransaction) -#pragma warning restore 618 { + Session = session; + _connection = suppliedConnection; + _connectionReleaseMode = connectionReleaseMode; + + _interceptor = interceptor; + _batcher = session.Factory.Settings.BatcherFactory.CreateBatcher(this, interceptor); + + _ownConnection = suppliedConnection == null; + ShouldAutoJoinTransaction = shouldAutoJoinTransaction; _connectionAccess = connectionAccess ?? throw new ArgumentNullException(nameof(connectionAccess)); } @@ -106,6 +113,7 @@ public ConnectionManager( _ownConnection = suppliedConnection == null; ShouldAutoJoinTransaction = shouldAutoJoinTransaction; + _connectionAccess = new NonContextualConnectionAccess(session.Factory); } public void AddDependentSession(ISessionImplementor session) From ebcb66874aa989e6d0e2512f2584806520dc792a Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Fri, 26 Jun 2020 13:43:08 +0300 Subject: [PATCH 22/27] fixup! Avoid breaking change --- src/NHibernate/AdoNet/ConnectionManager.cs | 16 +++------------- src/NHibernate/Impl/AbstractSessionImpl.cs | 4 ++-- 2 files changed, 5 insertions(+), 15 deletions(-) diff --git a/src/NHibernate/AdoNet/ConnectionManager.cs b/src/NHibernate/AdoNet/ConnectionManager.cs index 37fd4882c67..5ebf51a7fb1 100644 --- a/src/NHibernate/AdoNet/ConnectionManager.cs +++ b/src/NHibernate/AdoNet/ConnectionManager.cs @@ -80,8 +80,8 @@ public ConnectionManager( DbConnection suppliedConnection, ConnectionReleaseMode connectionReleaseMode, IInterceptor interceptor, - IConnectionAccess connectionAccess, - bool shouldAutoJoinTransaction) + bool shouldAutoJoinTransaction, + IConnectionAccess connectionAccess) { Session = session; _connection = suppliedConnection; @@ -102,18 +102,8 @@ public ConnectionManager( DbConnection suppliedConnection, ConnectionReleaseMode connectionReleaseMode, IInterceptor interceptor, - bool shouldAutoJoinTransaction) + bool shouldAutoJoinTransaction) : this(session, suppliedConnection, connectionReleaseMode, interceptor, shouldAutoJoinTransaction, new NonContextualConnectionAccess(session.Factory)) { - Session = session; - _connection = suppliedConnection; - _connectionReleaseMode = connectionReleaseMode; - - _interceptor = interceptor; - _batcher = session.Factory.Settings.BatcherFactory.CreateBatcher(this, interceptor); - - _ownConnection = suppliedConnection == null; - ShouldAutoJoinTransaction = shouldAutoJoinTransaction; - _connectionAccess = new NonContextualConnectionAccess(session.Factory); } public void AddDependentSession(ISessionImplementor session) diff --git a/src/NHibernate/Impl/AbstractSessionImpl.cs b/src/NHibernate/Impl/AbstractSessionImpl.cs index c3b5eeacc8a..a734cc79ec8 100644 --- a/src/NHibernate/Impl/AbstractSessionImpl.cs +++ b/src/NHibernate/Impl/AbstractSessionImpl.cs @@ -113,10 +113,10 @@ protected internal AbstractSessionImpl(ISessionFactoryImplementor factory, ISess options.UserSuppliedConnection, options.SessionConnectionReleaseMode, Interceptor, + options.ShouldAutoJoinTransaction, _tenantConfiguration == null ? new NonContextualConnectionAccess(_factory) - : _factory.Settings.MultiTenancyConnectionProvider.GetConnectionAccess(_tenantConfiguration, _factory), - options.ShouldAutoJoinTransaction); + : _factory.Settings.MultiTenancyConnectionProvider.GetConnectionAccess(_tenantConfiguration, _factory)); } } } From f368c66799723ddd90460c2b54f072e26d1f947b Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Sun, 5 Jul 2020 13:53:38 +0300 Subject: [PATCH 23/27] Code review changes --- src/NHibernate/Cache/CacheKey.cs | 7 +------ src/NHibernate/Cache/QueryKey.cs | 23 +---------------------- src/NHibernate/Impl/SessionFactoryImpl.cs | 2 +- 3 files changed, 3 insertions(+), 29 deletions(-) diff --git a/src/NHibernate/Cache/CacheKey.cs b/src/NHibernate/Cache/CacheKey.cs index e0890cfe272..71df0f99860 100644 --- a/src/NHibernate/Cache/CacheKey.cs +++ b/src/NHibernate/Cache/CacheKey.cs @@ -46,13 +46,8 @@ public CacheKey(object id, IType type, string entityOrRoleName, ISessionFactoryI //Since 5.3 [Obsolete("Use constructor with tenantIdentifier")] public CacheKey(object id, IType type, string entityOrRoleName, ISessionFactoryImplementor factory) + : this(id, type, entityOrRoleName, factory, null) { - key = id; - this.type = type; - this.entityOrRoleName = entityOrRoleName; - _factory = factory; - - _hashCode = GenerateHashCode(); } //Mainly for SysCache and Memcache diff --git a/src/NHibernate/Cache/QueryKey.cs b/src/NHibernate/Cache/QueryKey.cs index 83f97533437..d336a58d323 100644 --- a/src/NHibernate/Cache/QueryKey.cs +++ b/src/NHibernate/Cache/QueryKey.cs @@ -81,29 +81,8 @@ public QueryKey(ISessionFactoryImplementor factory, SqlString queryString, Query [Obsolete("Please use overload with tenantIdentifier")] public QueryKey(ISessionFactoryImplementor factory, SqlString queryString, QueryParameters queryParameters, ISet filters, CacheableResultTransformer customTransformer) + : this(factory, queryString, queryParameters, filters, customTransformer, null) { - _factory = factory; - _sqlQueryString = queryString; - _types = queryParameters.PositionalParameterTypes; - _values = queryParameters.PositionalParameterValues; - - RowSelection selection = queryParameters.RowSelection; - if (selection != null) - { - _firstRow = selection.FirstRow; - _maxRows = selection.MaxRows; - } - else - { - _firstRow = RowSelection.NoValue; - _maxRows = RowSelection.NoValue; - } - - _namedParameters = queryParameters.NamedParameters?.ToArray(); - _filters = filters?.ToArray(); - _customTransformer = customTransformer; - - _hashCode = ComputeHashCode(); } public CacheableResultTransformer ResultTransformer diff --git a/src/NHibernate/Impl/SessionFactoryImpl.cs b/src/NHibernate/Impl/SessionFactoryImpl.cs index d71be14ac4c..5d02abf1201 100644 --- a/src/NHibernate/Impl/SessionFactoryImpl.cs +++ b/src/NHibernate/Impl/SessionFactoryImpl.cs @@ -1038,7 +1038,7 @@ private CacheKey GenerateCacheKeyForEvict(object id, IType type, string entityOr throw new NotImplementedException("Eviction is not implemented for multi-tenancy. Please initialize CurrentSessionContext."); } - return new CacheKey(id, type, entityOrRoleName, this, null); + return new CacheKey(id, type, entityOrRoleName, this, tenantIdentifier); } public void EvictCollection(string roleName) From d74adedeff908c582d4c47486dbecce2c70ced6f Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Sun, 5 Jul 2020 14:49:47 +0300 Subject: [PATCH 24/27] Add multi-tenancy EvictEntity and EvictCollection --- .../Async/AdoNet/ConnectionManager.cs | 1 + src/NHibernate/Async/ISessionFactory.cs | 39 ++++++++++++++++ .../Async/Impl/SessionFactoryImpl.cs | 46 ++++++++++--------- src/NHibernate/Cache/CacheKey.cs | 5 +- src/NHibernate/ISessionFactory.cs | 35 ++++++++++++++ src/NHibernate/Impl/SessionFactoryImpl.cs | 45 +++++++++++------- 6 files changed, 132 insertions(+), 39 deletions(-) diff --git a/src/NHibernate/Async/AdoNet/ConnectionManager.cs b/src/NHibernate/Async/AdoNet/ConnectionManager.cs index a716845c6e9..90487416762 100644 --- a/src/NHibernate/Async/AdoNet/ConnectionManager.cs +++ b/src/NHibernate/Async/AdoNet/ConnectionManager.cs @@ -16,6 +16,7 @@ using System.Security; using NHibernate.Connection; using NHibernate.Engine; +using NHibernate.Impl; namespace NHibernate.AdoNet { diff --git a/src/NHibernate/Async/ISessionFactory.cs b/src/NHibernate/Async/ISessionFactory.cs index 744c59b3bde..482f8af4ea5 100644 --- a/src/NHibernate/Async/ISessionFactory.cs +++ b/src/NHibernate/Async/ISessionFactory.cs @@ -17,6 +17,7 @@ using NHibernate.Impl; using NHibernate.Metadata; using NHibernate.Stat; +using NHibernate.Util; namespace NHibernate { @@ -24,6 +25,44 @@ namespace NHibernate using System.Threading; public static partial class SessionFactoryExtension { + /// + /// Evict an entry from the second-level cache. This method occurs outside + /// of any transaction; it performs an immediate "hard" remove, so does not respect + /// any transaction isolation semantics of the usage strategy. Use with care. + /// + /// The session factory. + /// The name of the entity to evict. + /// + /// Tenant identifier + /// A cancellation token that can be used to cancel the work + public static async Task EvictEntityAsync(this ISessionFactory factory, string entityName, object id, string tenantIdentifier, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + if (tenantIdentifier == null) + await (factory.EvictEntityAsync(entityName, id, cancellationToken)).ConfigureAwait(false); + + await (ReflectHelper.CastOrThrow(factory, "multi-tenancy").EvictEntityAsync(entityName, id, tenantIdentifier, cancellationToken)).ConfigureAwait(false); + } + + /// + /// Evict an entry from the process-level cache. This method occurs outside + /// of any transaction; it performs an immediate "hard" remove, so does not respect + /// any transaction isolation semantics of the usage strategy. Use with care. + /// + /// The session factory. + /// Collection role name. + /// Collection id + /// Tenant identifier + /// A cancellation token that can be used to cancel the work + public static async Task EvictCollectionAsync(this ISessionFactory factory, string roleName, object id, string tenantIdentifier, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + if (tenantIdentifier == null) + await (factory.EvictCollectionAsync(roleName, id, cancellationToken)).ConfigureAwait(false); + + await (ReflectHelper.CastOrThrow(factory, "multi-tenancy").EvictCollectionAsync(roleName, id, tenantIdentifier, cancellationToken)).ConfigureAwait(false); + } + /// /// Evict all entries from the process-level cache. This method occurs outside /// of any transaction; it performs an immediate "hard" remove, so does not respect diff --git a/src/NHibernate/Async/Impl/SessionFactoryImpl.cs b/src/NHibernate/Async/Impl/SessionFactoryImpl.cs index dbcbdab24d0..50d4a4d6a11 100644 --- a/src/NHibernate/Async/Impl/SessionFactoryImpl.cs +++ b/src/NHibernate/Async/Impl/SessionFactoryImpl.cs @@ -132,24 +132,7 @@ public sealed partial class SessionFactoryImpl : ISessionFactoryImplementor, IOb { return Task.FromCanceled(cancellationToken); } - try - { - IEntityPersister p = GetEntityPersister(persistentClass.FullName); - if (p.HasCache) - { - if (log.IsDebugEnabled()) - { - log.Debug("evicting second-level cache: {0}", MessageHelper.InfoString(p, id)); - } - CacheKey ck = GenerateCacheKeyForEvict(id, p.IdentifierType, p.RootEntityName); - return p.Cache.RemoveAsync(ck, cancellationToken); - } - return Task.CompletedTask; - } - catch (Exception ex) - { - return Task.FromException(ex); - } + return EvictEntityAsync(persistentClass.FullName, id, cancellationToken); } public Task EvictAsync(System.Type persistentClass, CancellationToken cancellationToken = default(CancellationToken)) @@ -245,6 +228,15 @@ async Task InternalEvictEntityAsync() } public Task EvictEntityAsync(string entityName, object id, CancellationToken cancellationToken = default(CancellationToken)) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + return EvictEntityAsync(entityName, id, null, cancellationToken); + } + + public Task EvictEntityAsync(string entityName, object id, string tenantIdentifier, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) { @@ -257,9 +249,9 @@ async Task InternalEvictEntityAsync() { if (log.IsDebugEnabled()) { - log.Debug("evicting second-level cache: {0}", MessageHelper.InfoString(p, id, this)); + LogEvict(tenantIdentifier, MessageHelper.InfoString(p, id, this)); } - CacheKey cacheKey = GenerateCacheKeyForEvict(id, p.IdentifierType, p.RootEntityName); + CacheKey cacheKey = GenerateCacheKeyForEvict(id, p.IdentifierType, p.RootEntityName, tenantIdentifier); return p.Cache.RemoveAsync(cacheKey, cancellationToken); } return Task.CompletedTask; @@ -271,6 +263,15 @@ async Task InternalEvictEntityAsync() } public Task EvictCollectionAsync(string roleName, object id, CancellationToken cancellationToken = default(CancellationToken)) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + return EvictCollectionAsync(roleName, id, null, cancellationToken); + } + + public Task EvictCollectionAsync(string roleName, object id, string tenantIdentifier, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) { @@ -283,9 +284,10 @@ async Task InternalEvictEntityAsync() { if (log.IsDebugEnabled()) { - log.Debug("evicting second-level cache: {0}", MessageHelper.CollectionInfoString(p, id)); + LogEvict(tenantIdentifier, MessageHelper.CollectionInfoString(p, id)); } - CacheKey ck = GenerateCacheKeyForEvict(id, p.KeyType, p.Role); + + CacheKey ck = GenerateCacheKeyForEvict(id, p.KeyType, p.Role, tenantIdentifier); return p.Cache.RemoveAsync(ck, cancellationToken); } return Task.CompletedTask; diff --git a/src/NHibernate/Cache/CacheKey.cs b/src/NHibernate/Cache/CacheKey.cs index 71df0f99860..f3f050175dd 100644 --- a/src/NHibernate/Cache/CacheKey.cs +++ b/src/NHibernate/Cache/CacheKey.cs @@ -54,7 +54,10 @@ public CacheKey(object id, IType type, string entityOrRoleName, ISessionFactoryI public override String ToString() { // For Component the user can override ToString - return entityOrRoleName + '#' + key; + return + string.IsNullOrEmpty(_tenantIdentifier) + ? entityOrRoleName + "#" + key + : string.Join("#", entityOrRoleName, key, _tenantIdentifier); } public override bool Equals(object obj) diff --git a/src/NHibernate/ISessionFactory.cs b/src/NHibernate/ISessionFactory.cs index 8e85f6258d0..4dee98fde1d 100644 --- a/src/NHibernate/ISessionFactory.cs +++ b/src/NHibernate/ISessionFactory.cs @@ -7,12 +7,47 @@ using NHibernate.Impl; using NHibernate.Metadata; using NHibernate.Stat; +using NHibernate.Util; namespace NHibernate { // 6.0 TODO: move below methods directly in ISessionFactory then remove SessionFactoryExtension public static partial class SessionFactoryExtension { + /// + /// Evict an entry from the second-level cache. This method occurs outside + /// of any transaction; it performs an immediate "hard" remove, so does not respect + /// any transaction isolation semantics of the usage strategy. Use with care. + /// + /// The session factory. + /// The name of the entity to evict. + /// + /// Tenant identifier + public static void EvictEntity(this ISessionFactory factory, string entityName, object id, string tenantIdentifier) + { + if (tenantIdentifier == null) + factory.EvictEntity(entityName, id); + + ReflectHelper.CastOrThrow(factory, "multi-tenancy").EvictEntity(entityName, id, tenantIdentifier); + } + + /// + /// Evict an entry from the process-level cache. This method occurs outside + /// of any transaction; it performs an immediate "hard" remove, so does not respect + /// any transaction isolation semantics of the usage strategy. Use with care. + /// + /// The session factory. + /// Collection role name. + /// Collection id + /// Tenant identifier + public static void EvictCollection(this ISessionFactory factory, string roleName, object id, string tenantIdentifier) + { + if (tenantIdentifier == null) + factory.EvictCollection(roleName, id); + + ReflectHelper.CastOrThrow(factory, "multi-tenancy").EvictCollection(roleName, id, tenantIdentifier); + } + /// /// Evict all entries from the process-level cache. This method occurs outside /// of any transaction; it performs an immediate "hard" remove, so does not respect diff --git a/src/NHibernate/Impl/SessionFactoryImpl.cs b/src/NHibernate/Impl/SessionFactoryImpl.cs index 5d02abf1201..a4b72cea96d 100644 --- a/src/NHibernate/Impl/SessionFactoryImpl.cs +++ b/src/NHibernate/Impl/SessionFactoryImpl.cs @@ -933,16 +933,7 @@ public void Close() public void Evict(System.Type persistentClass, object id) { - IEntityPersister p = GetEntityPersister(persistentClass.FullName); - if (p.HasCache) - { - if (log.IsDebugEnabled()) - { - log.Debug("evicting second-level cache: {0}", MessageHelper.InfoString(p, id)); - } - CacheKey ck = GenerateCacheKeyForEvict(id, p.IdentifierType, p.RootEntityName); - p.Cache.Remove(ck); - } + EvictEntity(persistentClass.FullName, id); } public void Evict(System.Type persistentClass) @@ -995,34 +986,45 @@ public void EvictEntity(IEnumerable entityNames) } public void EvictEntity(string entityName, object id) + { + EvictEntity(entityName, id, null); + } + + public void EvictEntity(string entityName, object id, string tenantIdentifier) { IEntityPersister p = GetEntityPersister(entityName); if (p.HasCache) { if (log.IsDebugEnabled()) { - log.Debug("evicting second-level cache: {0}", MessageHelper.InfoString(p, id, this)); + LogEvict(tenantIdentifier, MessageHelper.InfoString(p, id, this)); } - CacheKey cacheKey = GenerateCacheKeyForEvict(id, p.IdentifierType, p.RootEntityName); + CacheKey cacheKey = GenerateCacheKeyForEvict(id, p.IdentifierType, p.RootEntityName, tenantIdentifier); p.Cache.Remove(cacheKey); } } public void EvictCollection(string roleName, object id) + { + EvictCollection(roleName, id, null); + } + + public void EvictCollection(string roleName, object id, string tenantIdentifier) { ICollectionPersister p = GetCollectionPersister(roleName); if (p.HasCache) { if (log.IsDebugEnabled()) { - log.Debug("evicting second-level cache: {0}", MessageHelper.CollectionInfoString(p, id)); + LogEvict(tenantIdentifier, MessageHelper.CollectionInfoString(p, id)); } - CacheKey ck = GenerateCacheKeyForEvict(id, p.KeyType, p.Role); + + CacheKey ck = GenerateCacheKeyForEvict(id, p.KeyType, p.Role, tenantIdentifier); p.Cache.Remove(ck); } } - private CacheKey GenerateCacheKeyForEvict(object id, IType type, string entityOrRoleName, string tenantIdentifier = null) + private CacheKey GenerateCacheKeyForEvict(object id, IType type, string entityOrRoleName, string tenantIdentifier) { // if there is a session context, use that to generate the key. if (CurrentSessionContext != null) @@ -1035,7 +1037,7 @@ private CacheKey GenerateCacheKeyForEvict(object id, IType type, string entityOr if (settings.MultiTenancyStrategy != MultiTenancyStrategy.None && tenantIdentifier == null) { - throw new NotImplementedException("Eviction is not implemented for multi-tenancy. Please initialize CurrentSessionContext."); + throw new ArgumentException("Use overload with tenantIdentifier or initialize CurrentSessionContext."); } return new CacheKey(id, type, entityOrRoleName, this, tenantIdentifier); @@ -1269,6 +1271,17 @@ public QueryPlanCache QueryPlanCache #endregion + private static void LogEvict(string tenantIdentifier, string infoString) + { + if (string.IsNullOrEmpty(tenantIdentifier)) + { + log.Debug("evicting second-level cache: {0}", infoString); + return; + } + + log.Debug("evicting second-level cache for tenant '{1}': {0}", infoString, tenantIdentifier); + } + private void Init() { statistics = new StatisticsImpl(this); From 6e39aa1da13d8378fb3adb8b75e8a60a44af83ba Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Sun, 5 Jul 2020 14:52:02 +0300 Subject: [PATCH 25/27] Code review changes --- src/NHibernate/MultiTenancy/MultiTenancyStrategy.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/NHibernate/MultiTenancy/MultiTenancyStrategy.cs b/src/NHibernate/MultiTenancy/MultiTenancyStrategy.cs index 98e93c3921f..cca618ad39a 100644 --- a/src/NHibernate/MultiTenancy/MultiTenancyStrategy.cs +++ b/src/NHibernate/MultiTenancy/MultiTenancyStrategy.cs @@ -11,6 +11,10 @@ public enum MultiTenancyStrategy /// None, + /// + /// Multi-tenancy implemented as separate database per tenant. + /// + Database, // /// // /// Multi-tenancy implemented by use of discriminator columns. // /// @@ -20,10 +24,5 @@ public enum MultiTenancyStrategy // /// Multi-tenancy implemented as separate schemas. // /// // Schema, - - /// - /// Multi-tenancy implemented as separate database per tenant. - /// - Database, } } From a4013aca44ef320038fe1944324f8b5ade3f14e9 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Sun, 5 Jul 2020 15:16:50 +0300 Subject: [PATCH 26/27] Code review changes. Made GetConnection() virtual --- .../NamedConnectionStringFixture.cs | 3 +-- .../NamedConnectionStringFixture.cs | 2 +- .../Async/Connection/ConnectionProvider.cs | 15 +++++++++++-- .../Connection/DriverConnectionProvider.cs | 10 --------- .../UserSuppliedConnectionProvider.cs | 3 +-- .../Connection/ConnectionProvider.cs | 21 ++++++++++++------- .../Connection/DriverConnectionProvider.cs | 5 ----- .../UserSuppliedConnectionProvider.cs | 2 +- 8 files changed, 31 insertions(+), 30 deletions(-) diff --git a/src/NHibernate.Test/Async/ConnectionStringTest/NamedConnectionStringFixture.cs b/src/NHibernate.Test/Async/ConnectionStringTest/NamedConnectionStringFixture.cs index 8d3b82709d0..48a945bba1c 100644 --- a/src/NHibernate.Test/Async/ConnectionStringTest/NamedConnectionStringFixture.cs +++ b/src/NHibernate.Test/Async/ConnectionStringTest/NamedConnectionStringFixture.cs @@ -26,9 +26,8 @@ public partial class MockConnectionProvider : ConnectionProvider /// /// Get an open . /// - /// A cancellation token that can be used to cancel the work /// An open . - public override Task GetConnectionAsync(CancellationToken cancellationToken) + public override Task GetConnectionAsync(string connectionString, CancellationToken cancellationToken) { throw new NotImplementedException(); } diff --git a/src/NHibernate.Test/ConnectionStringTest/NamedConnectionStringFixture.cs b/src/NHibernate.Test/ConnectionStringTest/NamedConnectionStringFixture.cs index 5030067e5e8..5933d5bb65d 100644 --- a/src/NHibernate.Test/ConnectionStringTest/NamedConnectionStringFixture.cs +++ b/src/NHibernate.Test/ConnectionStringTest/NamedConnectionStringFixture.cs @@ -58,7 +58,7 @@ public string PublicConnectionString /// Get an open . /// /// An open . - public override DbConnection GetConnection() + public override DbConnection GetConnection(string connectionString) { throw new NotImplementedException(); } diff --git a/src/NHibernate/Async/Connection/ConnectionProvider.cs b/src/NHibernate/Async/Connection/ConnectionProvider.cs index fbdaac63ad4..df3ab7cced7 100644 --- a/src/NHibernate/Async/Connection/ConnectionProvider.cs +++ b/src/NHibernate/Async/Connection/ConnectionProvider.cs @@ -29,12 +29,23 @@ public abstract partial class ConnectionProvider : IConnectionProvider /// /// A cancellation token that can be used to cancel the work /// An open . - public abstract Task GetConnectionAsync(CancellationToken cancellationToken); + public virtual Task GetConnectionAsync(CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + return GetConnectionAsync(ConnectionString, cancellationToken); + } //TODO 6.0: Make abstract + /// + /// Gets an open for given connectionString + /// + /// An open . public virtual Task GetConnectionAsync(string connectionString, CancellationToken cancellationToken) { - throw new NotImplementedException(); + throw new NotImplementedException("This method must be overriden."); } } } diff --git a/src/NHibernate/Async/Connection/DriverConnectionProvider.cs b/src/NHibernate/Async/Connection/DriverConnectionProvider.cs index 72a96753f56..c0c21554592 100644 --- a/src/NHibernate/Async/Connection/DriverConnectionProvider.cs +++ b/src/NHibernate/Async/Connection/DriverConnectionProvider.cs @@ -22,22 +22,12 @@ public partial class DriverConnectionProvider : ConnectionProvider /// Gets a new open through /// the . /// - /// A cancellation token that can be used to cancel the work /// /// An Open . /// /// /// If there is any problem creating or opening the . /// - public override Task GetConnectionAsync(CancellationToken cancellationToken) - { - if (cancellationToken.IsCancellationRequested) - { - return Task.FromCanceled(cancellationToken); - } - return GetConnectionAsync(ConnectionString, cancellationToken); - } - public override async Task GetConnectionAsync(string connectionString, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); diff --git a/src/NHibernate/Async/Connection/UserSuppliedConnectionProvider.cs b/src/NHibernate/Async/Connection/UserSuppliedConnectionProvider.cs index 23422dd1bc8..a4950405bf4 100644 --- a/src/NHibernate/Async/Connection/UserSuppliedConnectionProvider.cs +++ b/src/NHibernate/Async/Connection/UserSuppliedConnectionProvider.cs @@ -23,7 +23,6 @@ public partial class UserSuppliedConnectionProvider : ConnectionProvider /// Throws an if this method is called /// because the user is responsible for creating s. /// - /// A cancellation token that can be used to cancel the work /// /// No value is returned because an is thrown. /// @@ -31,7 +30,7 @@ public partial class UserSuppliedConnectionProvider : ConnectionProvider /// Thrown when this method is called. User is responsible for creating /// s. /// - public override Task GetConnectionAsync(CancellationToken cancellationToken) + public override Task GetConnectionAsync(string connectionString, CancellationToken cancellationToken) { throw new InvalidOperationException("The user must provide an ADO.NET connection - NHibernate is not creating it."); } diff --git a/src/NHibernate/Connection/ConnectionProvider.cs b/src/NHibernate/Connection/ConnectionProvider.cs index ef22ab229f0..19293b5ca0e 100644 --- a/src/NHibernate/Connection/ConnectionProvider.cs +++ b/src/NHibernate/Connection/ConnectionProvider.cs @@ -139,7 +139,20 @@ public IDriver Driver /// Get an open . /// /// An open . - public abstract DbConnection GetConnection(); + public virtual DbConnection GetConnection() + { + return GetConnection(ConnectionString); + } + + //TODO 6.0: Make abstract + /// + /// Gets an open for given connectionString + /// + /// An open . + public virtual DbConnection GetConnection(string connectionString) + { + throw new NotImplementedException("This method must be overriden."); + } #region IDisposable Members @@ -204,11 +217,5 @@ protected virtual void Dispose(bool isDisposing) } #endregion - - //TODO 6.0: Make abstract - public virtual DbConnection GetConnection(string connectionString) - { - throw new NotImplementedException(); - } } } diff --git a/src/NHibernate/Connection/DriverConnectionProvider.cs b/src/NHibernate/Connection/DriverConnectionProvider.cs index a11c0f41d09..94503d0ca75 100644 --- a/src/NHibernate/Connection/DriverConnectionProvider.cs +++ b/src/NHibernate/Connection/DriverConnectionProvider.cs @@ -30,11 +30,6 @@ public override void CloseConnection(DbConnection conn) /// /// If there is any problem creating or opening the . /// - public override DbConnection GetConnection() - { - return GetConnection(ConnectionString); - } - public override DbConnection GetConnection(string connectionString) { log.Debug("Obtaining DbConnection from Driver"); diff --git a/src/NHibernate/Connection/UserSuppliedConnectionProvider.cs b/src/NHibernate/Connection/UserSuppliedConnectionProvider.cs index cbdf5bb3f16..ff15a643252 100644 --- a/src/NHibernate/Connection/UserSuppliedConnectionProvider.cs +++ b/src/NHibernate/Connection/UserSuppliedConnectionProvider.cs @@ -40,7 +40,7 @@ public override void CloseConnection(DbConnection conn) /// Thrown when this method is called. User is responsible for creating /// s. /// - public override DbConnection GetConnection() + public override DbConnection GetConnection(string connectionString) { throw new InvalidOperationException("The user must provide an ADO.NET connection - NHibernate is not creating it."); } From f4dc1855b077a2d2aae8d66f6b736e7f13543cf1 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Sun, 5 Jul 2020 15:28:31 +0300 Subject: [PATCH 27/27] Expose session TenantConfiguration --- src/NHibernate/Engine/ISessionImplementor.cs | 6 +++--- src/NHibernate/Impl/AbstractSessionImpl.cs | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/NHibernate/Engine/ISessionImplementor.cs b/src/NHibernate/Engine/ISessionImplementor.cs index 2df29ee3e46..9a0cb58b77a 100644 --- a/src/NHibernate/Engine/ISessionImplementor.cs +++ b/src/NHibernate/Engine/ISessionImplementor.cs @@ -18,10 +18,10 @@ namespace NHibernate.Engine { - // 6.0 TODO: Convert to interface methods, excepted SwitchCacheMode + // 6.0 TODO: Convert to interface methods, excepted SwitchCacheMode, GetTenantIdentifier public static partial class SessionImplementorExtensions { - //6.0 TODO: Expose as TenantIdentifier property + //NOTE: Keep it as extension /// /// Obtain the tenant identifier associated with this session. /// @@ -30,7 +30,7 @@ public static string GetTenantIdentifier(this ISessionImplementor session) { if (session is AbstractSessionImpl sessionImpl) { - return sessionImpl.TenantIdentifier; + return sessionImpl.TenantConfiguration?.TenantIdentifier; } return null; diff --git a/src/NHibernate/Impl/AbstractSessionImpl.cs b/src/NHibernate/Impl/AbstractSessionImpl.cs index a734cc79ec8..d2fd81b1b72 100644 --- a/src/NHibernate/Impl/AbstractSessionImpl.cs +++ b/src/NHibernate/Impl/AbstractSessionImpl.cs @@ -498,7 +498,8 @@ protected bool IsAlreadyDisposed /// public virtual bool TransactionInProgress => ConnectionManager.IsInActiveTransaction; - protected internal TenantConfiguration TenantConfiguration + //6.0 TODO Add to ISessionImplementor + public TenantConfiguration TenantConfiguration { get => _tenantConfiguration; protected set => _tenantConfiguration = value;