diff --git a/src/NHibernate.Test/ConnectionTest/AggressiveReleaseTest.cs b/src/NHibernate.Test/ConnectionTest/AggressiveReleaseTest.cs index 1fb7735f136..65bbeb00fb9 100644 --- a/src/NHibernate.Test/ConnectionTest/AggressiveReleaseTest.cs +++ b/src/NHibernate.Test/ConnectionTest/AggressiveReleaseTest.cs @@ -176,23 +176,24 @@ public void SuppliedConnection() { Prepare(); - DbConnection originalConnection = sessions.ConnectionProvider.GetConnection(); - ISession session = sessions.OpenSession(originalConnection); - - Silly silly = new Silly("silly"); - session.Save(silly); + using (var originalConnection = sessions.ConnectionProvider.GetConnection()) + using (var session = sessions.WithOptions().Connection(originalConnection).OpenSession()) + { + var silly = new Silly("silly"); + session.Save(silly); - // this will cause the connection manager to cycle through the aggressive Release logic; - // it should not Release the connection since we explicitly suplied it ourselves. - session.Flush(); + // this will cause the connection manager to cycle through the aggressive Release logic; + // it should not Release the connection since we explicitly supplied it ourselves. + session.Flush(); - Assert.IsTrue(originalConnection == session.Connection, "Different connections"); + Assert.IsTrue(originalConnection == session.Connection, "Different connections"); - session.Delete(silly); - session.Flush(); + session.Delete(silly); + session.Flush(); - Release(session); - originalConnection.Close(); + Release(session); + originalConnection.Close(); + } Done(); } diff --git a/src/NHibernate.Test/Legacy/FooBarTest.cs b/src/NHibernate.Test/Legacy/FooBarTest.cs index 5ade062814e..e6995cfd992 100644 --- a/src/NHibernate.Test/Legacy/FooBarTest.cs +++ b/src/NHibernate.Test/Legacy/FooBarTest.cs @@ -204,7 +204,7 @@ public void WierdSession() using (ISession s = OpenSession()) { - s.FlushMode = FlushMode.Never; + s.FlushMode = FlushMode.Manual; using (ITransaction t = s.BeginTransaction()) { Foo foo = (Foo) s.Get(typeof(Foo), id); @@ -4977,21 +4977,27 @@ public void AutoFlushCollections() [Test] public void UserProvidedConnection() { - IConnectionProvider prov = ConnectionProviderFactory.NewConnectionProvider(cfg.Properties); - ISession s = sessions.OpenSession(prov.GetConnection()); - ITransaction tx = s.BeginTransaction(); - s.CreateQuery("from foo in class NHibernate.DomainModel.Fo").List(); - tx.Commit(); - - var c = s.Disconnect(); - Assert.IsNotNull(c); + using (var prov = ConnectionProviderFactory.NewConnectionProvider(cfg.Properties)) + using (var connection = prov.GetConnection()) + using (var s = sessions.WithOptions().Connection(connection).OpenSession()) + { + using (var tx = s.BeginTransaction()) + { + s.CreateQuery("from foo in class NHibernate.DomainModel.Fo").List(); + tx.Commit(); + } + var c = s.Disconnect(); + Assert.IsNotNull(c); - s.Reconnect(c); - tx = s.BeginTransaction(); - s.CreateQuery("from foo in class NHibernate.DomainModel.Fo").List(); - tx.Commit(); - Assert.AreSame(c, s.Close()); - c.Close(); + s.Reconnect(c); + using (var tx = s.BeginTransaction()) + { + s.CreateQuery("from foo in class NHibernate.DomainModel.Fo").List(); + tx.Commit(); + } + Assert.AreSame(c, s.Close()); + c.Close(); + } } [Test] @@ -5181,7 +5187,7 @@ public void EmbeddedCompositeID() s.Close(); s = OpenSession(); - s.FlushMode = FlushMode.Never; + s.FlushMode = FlushMode.Manual; l = (Location) s.CreateQuery("from l in class Location where l.CountryCode = 'AU' and l.Description='foo bar'").List()[0]; diff --git a/src/NHibernate.Test/Legacy/FumTest.cs b/src/NHibernate.Test/Legacy/FumTest.cs index 1ba7c5efef1..5f0ff1e9fcf 100644 --- a/src/NHibernate.Test/Legacy/FumTest.cs +++ b/src/NHibernate.Test/Legacy/FumTest.cs @@ -617,7 +617,7 @@ public void UnflushedSessionSerialization() // instead of just the usual openSession() using (ISession s = sessions.OpenSession()) { - s.FlushMode = FlushMode.Never; + s.FlushMode = FlushMode.Manual; Simple simple = new Simple(); simple.Address = "123 Main St. Anytown USA"; @@ -656,7 +656,7 @@ public void UnflushedSessionSerialization() using (ISession s = sessions.OpenSession()) { - s.FlushMode = FlushMode.Never; + s.FlushMode = FlushMode.Manual; Simple simple = (Simple) s.Get(typeof(Simple), 10L); Assert.AreEqual(check.Name, simple.Name, "Not same parent instances"); Assert.AreEqual(check.Other.Name, other.Name, "Not same child instances"); @@ -679,7 +679,7 @@ public void UnflushedSessionSerialization() // Test deletions across serializations using (ISession s = sessions.OpenSession()) { - s.FlushMode = FlushMode.Never; + s.FlushMode = FlushMode.Manual; Simple simple = (Simple) s.Get(typeof(Simple), 10L); Assert.AreEqual(check.Name, simple.Name, "Not same parent instances"); Assert.AreEqual(check.Other.Name, other.Name, "Not same child instances"); diff --git a/src/NHibernate.Test/NHSpecificTest/EmptyMappingsFixture.cs b/src/NHibernate.Test/NHSpecificTest/EmptyMappingsFixture.cs index 746500b6d04..6ff4df8bf58 100644 --- a/src/NHibernate.Test/NHSpecificTest/EmptyMappingsFixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/EmptyMappingsFixture.cs @@ -61,7 +61,7 @@ public void InvalidQuery() public void NullInterceptor() { IInterceptor nullInterceptor = null; - Assert.Throws(() => sessions.OpenSession(nullInterceptor).Close()); + Assert.Throws(() => sessions.WithOptions().Interceptor(nullInterceptor).OpenSession().Close()); } [Test] diff --git a/src/NHibernate.Test/NHSpecificTest/NH1082/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH1082/Fixture.cs index 54398f9941b..31c961c8d6c 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1082/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1082/Fixture.cs @@ -16,15 +16,11 @@ public override string BugNumber [Test] public void ExceptionsInBeforeTransactionCompletionAbortTransaction() { -#pragma warning disable 618 - Assert.IsFalse(sessions.Settings.IsInterceptorsBeforeTransactionCompletionIgnoreExceptionsEnabled); -#pragma warning restore 618 - var c = new C {ID = 1, Value = "value"}; var sessionInterceptor = new SessionInterceptorThatThrowsExceptionAtBeforeTransactionCompletion(); - using (ISession s = sessions.OpenSession(sessionInterceptor)) - using (ITransaction t = s.BeginTransaction()) + using (var s = sessions.WithOptions().Interceptor(sessionInterceptor).OpenSession()) + using (var t = s.BeginTransaction()) { s.Save(c); @@ -42,10 +38,6 @@ public void ExceptionsInBeforeTransactionCompletionAbortTransaction() [Test] public void ExceptionsInSynchronizationBeforeTransactionCompletionAbortTransaction() { -#pragma warning disable 618 - Assert.IsFalse(sessions.Settings.IsInterceptorsBeforeTransactionCompletionIgnoreExceptionsEnabled); -#pragma warning restore 618 - var c = new C { ID = 1, Value = "value" }; var synchronization = new SynchronizationThatThrowsExceptionAtBeforeTransactionCompletion(); @@ -66,78 +58,4 @@ public void ExceptionsInSynchronizationBeforeTransactionCompletionAbortTransacti } } } - - - [TestFixture] - [Obsolete("Can be removed when Environment.InterceptorsBeforeTransactionCompletionIgnoreExceptions is removed.")] - public class OldBehaviorEnabledFixture : BugTestCase - { - public override string BugNumber - { - get { return "NH1082"; } - } - - protected override void Configure(Configuration configuration) - { - configuration.SetProperty(Environment.InterceptorsBeforeTransactionCompletionIgnoreExceptions, "true"); - base.Configure(configuration); - } - - [Test] - public void ExceptionsInBeforeTransactionCompletionAreIgnored() - { - Assert.IsTrue(sessions.Settings.IsInterceptorsBeforeTransactionCompletionIgnoreExceptionsEnabled); - - var c = new C {ID = 1, Value = "value"}; - - var sessionInterceptor = new SessionInterceptorThatThrowsExceptionAtBeforeTransactionCompletion(); - using (ISession s = sessions.OpenSession(sessionInterceptor)) - using (ITransaction t = s.BeginTransaction()) - { - s.Save(c); - - Assert.DoesNotThrow(t.Commit); - } - - using (ISession s = sessions.OpenSession()) - { - var objectInDb = s.Get(1); - - Assert.IsNotNull(objectInDb); - - s.Delete(objectInDb); - s.Flush(); - } - } - - - [Test] - public void ExceptionsInSynchronizationBeforeTransactionCompletionAreIgnored() - { - Assert.IsTrue(sessions.Settings.IsInterceptorsBeforeTransactionCompletionIgnoreExceptionsEnabled); - - var c = new C { ID = 1, Value = "value" }; - - var synchronization = new SynchronizationThatThrowsExceptionAtBeforeTransactionCompletion(); - using (ISession s = sessions.OpenSession()) - using (ITransaction t = s.BeginTransaction()) - { - t.RegisterSynchronization(synchronization); - - s.Save(c); - - Assert.DoesNotThrow(t.Commit); - } - - using (ISession s = sessions.OpenSession()) - { - var objectInDb = s.Get(1); - - Assert.IsNotNull(objectInDb); - - s.Delete(objectInDb); - s.Flush(); - } - } - } } diff --git a/src/NHibernate.Test/NHSpecificTest/NH1159/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH1159/Fixture.cs index 66486341067..0e54f626526 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1159/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1159/Fixture.cs @@ -69,7 +69,7 @@ public void DoesNotFlushWithCriteriaWithNever() using (ISession session = OpenSession(new HibernateInterceptor())) using (ITransaction tran = session.BeginTransaction()) { - session.FlushMode = FlushMode.Never; + session.FlushMode = FlushMode.Manual; Assert.That(HibernateInterceptor.CallCount, Is.EqualTo(0)); Contact contact = session.Get((Int64)1); contact.PreferredName = "Updated preferred name"; diff --git a/src/NHibernate.Test/NHSpecificTest/NH1632/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH1632/Fixture.cs index f6ef9b06110..e2094a6a4f4 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1632/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1632/Fixture.cs @@ -106,7 +106,7 @@ public void When_commiting_items_in_DTC_transaction_will_add_items_to_2nd_level_ using (var tx = new TransactionScope()) { - using (var s = sessions.OpenSession(connection)) + using (var s = sessions.WithOptions().Connection(connection).OpenSession()) { var nums = s.Load(29); Assert.AreEqual(1, nums.NumA); @@ -156,7 +156,7 @@ public void Will_not_save_when_flush_mode_is_never() { using (ISession s = sessions.OpenSession()) { - s.FlushMode = FlushMode.Never; + s.FlushMode = FlushMode.Manual; id = s.Save(new Nums { NumA = 1, NumB = 2, ID = 5 }); } tx.Complete(); diff --git a/src/NHibernate.Test/NHSpecificTest/NH1714/SimpleReproductionFixture.cs b/src/NHibernate.Test/NHSpecificTest/NH1714/SimpleReproductionFixture.cs index eb4f1298c93..6cd431e518c 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1714/SimpleReproductionFixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1714/SimpleReproductionFixture.cs @@ -22,7 +22,7 @@ public void DbCommandsFromEventListenerShouldBeEnlistedInRunningTransaction() var entity = new DomainClass(); session.Save(entity); - using (var otherSession = session.GetChildSession()) + using (var otherSession = session.SessionWithOptions().Connection().OpenSession()) { otherSession.Save(new DomainClass()); otherSession.Flush(); diff --git a/src/NHibernate.Test/NHSpecificTest/NH1714/UseCaseDemonstrationFixture.cs b/src/NHibernate.Test/NHSpecificTest/NH1714/UseCaseDemonstrationFixture.cs index cad60907646..5dcc96c8147 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1714/UseCaseDemonstrationFixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1714/UseCaseDemonstrationFixture.cs @@ -60,7 +60,7 @@ public bool OnPreInsert(PreInsertEvent e) return false; // this will join into the parent's transaction - using (var session = e.Session.GetChildSession()) + using (var session = e.Session.SessionWithOptions().Connection().OpenSession()) { //should insert log record here session.Save(new LogClass()); diff --git a/src/NHibernate.Test/NHSpecificTest/NH2374/NH2374Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH2374/NH2374Fixture.cs index 0df9cb2e5ca..df5a2e76831 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2374/NH2374Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2374/NH2374Fixture.cs @@ -13,7 +13,7 @@ public void OneToOne_with_EntityMode_Map() using (ISession sroot = OpenSession()) { - using (ISession s = sroot.GetChildSession()) + using (ISession s = sroot.SessionWithOptions().Connection().OpenSession()) { using (ITransaction t = s.BeginTransaction()) { diff --git a/src/NHibernate.Test/NHSpecificTest/NH2420/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH2420/Fixture.cs index 706c92aba5b..e1bebdfea10 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2420/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2420/Fixture.cs @@ -73,7 +73,7 @@ public void ShouldBeAbleToReleaseSuppliedConnectionAfterDistributedTransaction() using (connection) { connection.Open(); - using (s = Sfi.OpenSession(connection)) + using (s = Sfi.WithOptions().Connection(connection).OpenSession()) { s.Save(new MyTable { String = "hello!" }); } diff --git a/src/NHibernate.Test/NHSpecificTest/NH3985/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH3985/Fixture.cs index b08e0fc8827..dc64d62a213 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH3985/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH3985/Fixture.cs @@ -14,11 +14,11 @@ public void GetChildSession_ShouldReturnNonDisposedInstance() { using (var rootSession = OpenSession()) { - using (var childSession1 = rootSession.GetChildSession()) + using (var childSession1 = rootSession.SessionWithOptions().Connection().OpenSession()) { } - using (var childSession2 = rootSession.GetChildSession()) + using (var childSession2 = rootSession.SessionWithOptions().Connection().OpenSession()) { Assert.DoesNotThrow(() => { childSession2.Get(Guid.NewGuid()); }); } @@ -30,10 +30,10 @@ public void GetChildSession_ShouldReturnNonClosedInstance() { using (var rootSession = OpenSession()) { - var childSession1 = rootSession.GetChildSession(); + var childSession1 = rootSession.SessionWithOptions().Connection().OpenSession(); childSession1.Close(); - using (var childSession2 = rootSession.GetChildSession()) + using (var childSession2 = rootSession.SessionWithOptions().Connection().OpenSession()) { Assert.DoesNotThrow(() => { childSession2.Get(Guid.NewGuid()); }); } diff --git a/src/NHibernate.Test/NHibernate.Test.csproj b/src/NHibernate.Test/NHibernate.Test.csproj index f46c51074ba..b0a870b8b9f 100644 --- a/src/NHibernate.Test/NHibernate.Test.csproj +++ b/src/NHibernate.Test/NHibernate.Test.csproj @@ -1420,6 +1420,8 @@ + + @@ -3269,6 +3271,7 @@ + diff --git a/src/NHibernate.Test/SessionBuilder/Entity.cs b/src/NHibernate.Test/SessionBuilder/Entity.cs new file mode 100644 index 00000000000..6dfc8375c80 --- /dev/null +++ b/src/NHibernate.Test/SessionBuilder/Entity.cs @@ -0,0 +1,10 @@ +using System; + +namespace NHibernate.Test.SessionBuilder +{ + class Entity + { + public virtual Guid Id { get; set; } + public virtual string Name { get; set; } + } +} \ No newline at end of file diff --git a/src/NHibernate.Test/SessionBuilder/Fixture.cs b/src/NHibernate.Test/SessionBuilder/Fixture.cs new file mode 100644 index 00000000000..67184e6a697 --- /dev/null +++ b/src/NHibernate.Test/SessionBuilder/Fixture.cs @@ -0,0 +1,227 @@ +using System; +using System.Collections; +using System.Linq; +using NHibernate.Cfg; +using NHibernate.Impl; +using NUnit.Framework; + +namespace NHibernate.Test.SessionBuilder +{ + public class Fixture : TestCase + { + protected override string MappingsAssembly => "NHibernate.Test"; + + protected override IList Mappings => + new string[] + { + "SessionBuilder.Mappings.hbm.xml" + }; + + protected override void Configure(Configuration configuration) + { + // Do not use .Instance here, we want another instance. + configuration.Interceptor = new EmptyInterceptor(); + } + + [Test] + public void CanSetAutoClose() + { + var sb = sessions.WithOptions(); + CanSetAutoClose(sb); + using (var s = sb.OpenSession()) + { + CanSetAutoClose(s.SessionWithOptions()); + } + } + + private void CanSetAutoClose(T sb) where T : ISessionBuilder + { + var options = (ISessionCreationOptions)sb; + CanSet(sb, sb.AutoClose, () => options.ShouldAutoClose, + sb is ISharedSessionBuilder ssb ? ssb.AutoClose : default(Func), + // initial values + false, + // values + true, false); + } + + [Test] + public void CanSetConnection() + { + var sb = sessions.WithOptions(); + CanSetConnection(sb); + using (var s = sb.OpenSession()) + { + CanSetConnection(s.SessionWithOptions()); + } + } + + private void CanSetConnection(T sb) where T : ISessionBuilder + { + var sbType = sb.GetType().Name; + var conn = sessions.ConnectionProvider.GetConnection(); + try + { + var options = (ISessionCreationOptions)sb; + Assert.IsNull(options.UserSuppliedConnection, $"{sbType}: Initial value"); + var fsb = sb.Connection(conn); + Assert.AreEqual(conn, options.UserSuppliedConnection, $"{sbType}: After call with a connection"); + Assert.AreEqual(sb, fsb, $"{sbType}: Unexpected fluent return after call with a connection"); + + if (sb is ISharedSessionBuilder ssb) + { + var sharedOptions = (ISharedSessionCreationOptions)options; + Assert.IsFalse(sharedOptions.IsTransactionCoordinatorShared, $"{sbType}: Transaction coordinator shared before sharing"); + Assert.IsNull(sharedOptions.ConnectionManager, $"{sbType}: Connection manager shared before sharing"); + + var fssb = ssb.Connection(); + // Sharing connection shares the connection manager, not the connection. + Assert.IsNull(options.UserSuppliedConnection, $"{sbType}: After call with previous session connection"); + Assert.IsTrue(sharedOptions.IsTransactionCoordinatorShared, $"{sbType}: Transaction coordinator not shared after sharing"); + Assert.IsNotNull(sharedOptions.ConnectionManager, $"{sbType}: Connection manager not shared after sharing"); + Assert.AreEqual(sb, fssb, $"{sbType}: Unexpected fluent return on shared"); + + fsb = sb.Connection(null); + Assert.IsNull(options.UserSuppliedConnection, $"{sbType}: After call with null"); + Assert.IsFalse(sharedOptions.IsTransactionCoordinatorShared, $"{sbType}: Transaction coordinator shared after un-sharing"); + Assert.IsNull(sharedOptions.ConnectionManager, $"{sbType}: Connection manager shared after un-sharing"); + Assert.AreEqual(sb, fsb, $"{sbType}: Unexpected fluent return after un-sharing"); + } + else + { + fsb = sb.Connection(null); + Assert.IsNull(options.UserSuppliedConnection, $"{sbType}: After call with null"); + Assert.AreEqual(sb, fsb, $"{sbType}: Unexpected fluent return after call with null"); + } + } + finally + { + sessions.ConnectionProvider.CloseConnection(conn); + } + } + + [Test] + public void CanSetConnectionOnStateless() + { + var sb = sessions.WithStatelessOptions(); + var sbType = sb.GetType().Name; + var conn = sessions.ConnectionProvider.GetConnection(); + try + { + var options = (ISessionCreationOptions)sb; + Assert.IsNull(options.UserSuppliedConnection, $"{sbType}: Initial value"); + var fsb = sb.Connection(conn); + Assert.AreEqual(conn, options.UserSuppliedConnection, $"{sbType}: After call with a connection"); + Assert.AreEqual(sb, fsb, $"{sbType}: Unexpected fluent return after call with a connection"); + + fsb = sb.Connection(null); + Assert.IsNull(options.UserSuppliedConnection, $"{sbType}: After call with null"); + Assert.AreEqual(sb, fsb, $"{sbType}: Unexpected fluent return after call with null"); + } + finally + { + sessions.ConnectionProvider.CloseConnection(conn); + } + } + + [Test] + public void CanSetConnectionReleaseMode() + { + var sb = sessions.WithOptions(); + CanSetConnectionReleaseMode(sb); + using (var s = sb.OpenSession()) + { + CanSetConnectionReleaseMode(s.SessionWithOptions()); + } + } + + private void CanSetConnectionReleaseMode(T sb) where T : ISessionBuilder + { + var options = (ISessionCreationOptions)sb; + CanSet(sb, sb.ConnectionReleaseMode, () => options.SessionConnectionReleaseMode, + sb is ISharedSessionBuilder ssb ? ssb.ConnectionReleaseMode : default(Func), + // initial values + sessions.Settings.ConnectionReleaseMode, + // values + ConnectionReleaseMode.OnClose, ConnectionReleaseMode.AfterStatement, ConnectionReleaseMode.AfterTransaction); + } + + [Test] + public void CanSetFlushMode() + { + var sb = sessions.WithOptions(); + CanSetFlushMode(sb); + using (var s = sb.OpenSession()) + { + CanSetFlushMode(s.SessionWithOptions()); + } + } + + private void CanSetFlushMode(T sb) where T : ISessionBuilder + { + var options = (ISessionCreationOptions)sb; + CanSet(sb, sb.FlushMode, () => options.InitialSessionFlushMode, + sb is ISharedSessionBuilder ssb ? ssb.FlushMode : default(Func), + // initial values + sessions.Settings.DefaultFlushMode, + // values + FlushMode.Always, FlushMode.Auto, FlushMode.Commit, FlushMode.Manual); + } + + [Test] + public void CanSetInterceptor() + { + var sb = sessions.WithOptions(); + CanSetInterceptor(sb); + using (var s = sb.OpenSession()) + { + CanSetInterceptor(s.SessionWithOptions()); + } + } + + private void CanSetInterceptor(T sb) where T : ISessionBuilder + { + var sbType = sb.GetType().Name; + // Do not use .Instance here, we want another instance. + var interceptor = new EmptyInterceptor(); + var options = (ISessionCreationOptions)sb; + + Assert.AreEqual(sessions.Interceptor, options.SessionInterceptor, $"{sbType}: Initial value"); + var fsb = sb.Interceptor(interceptor); + Assert.AreEqual(interceptor, options.SessionInterceptor, $"{sbType}: After call with an interceptor"); + Assert.AreEqual(sb, fsb, $"{sbType}: Unexpected fluent return after call with an interceptor"); + + if (sb is ISharedSessionBuilder ssb) + { + var fssb = ssb.Interceptor(); + Assert.AreEqual(EmptyInterceptor.Instance, options.SessionInterceptor, $"{sbType}: After call with shared interceptor"); + Assert.AreEqual(sb, fssb, $"{sbType}: Unexpected fluent return on shared"); + } + + Assert.Throws(() => sb.Interceptor(null), $"{sbType}: After call with null"); + + fsb = sb.NoInterceptor(); + Assert.AreEqual(EmptyInterceptor.Instance, options.SessionInterceptor, $"{sbType}: After no call"); + Assert.AreEqual(sb, fsb, $"{sbType}: Unexpected fluent return after no call"); + } + + private void CanSet(T sb, Func setter, Func getter, Func shared, V initialValue, + params V[] values) where T : ISessionBuilder + { + var sbType = sb.GetType().Name; + Assert.AreEqual(initialValue, getter(), $"{sbType}: Initial value"); + if (shared != null) + { + var fssb = shared(); + Assert.AreEqual(values.Last(), getter(), $"{sbType}: After call with shared setting"); + Assert.AreEqual(sb, fssb, $"{sbType}: Unexpected fluent return on shared"); + } + foreach (var value in values) + { + var fsb = setter(value); + Assert.AreEqual(value, getter(), $"{sbType}: After call with {value}"); + Assert.AreEqual(sb, fsb, $"{sbType}: Unexpected fluent return after call with {value}"); + } + } + } +} diff --git a/src/NHibernate.Test/SessionBuilder/Mappings.hbm.xml b/src/NHibernate.Test/SessionBuilder/Mappings.hbm.xml new file mode 100644 index 00000000000..2350f77fe4e --- /dev/null +++ b/src/NHibernate.Test/SessionBuilder/Mappings.hbm.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/NHibernate.Test/SystemTransactions/TransactionNotificationFixture.cs b/src/NHibernate.Test/SystemTransactions/TransactionNotificationFixture.cs index d3702e74b9c..db9c0b0fa4a 100644 --- a/src/NHibernate.Test/SystemTransactions/TransactionNotificationFixture.cs +++ b/src/NHibernate.Test/SystemTransactions/TransactionNotificationFixture.cs @@ -20,7 +20,7 @@ protected override IList Mappings public void NoTransaction() { var interceptor = new RecordingInterceptor(); - using (sessions.OpenSession(interceptor)) + using (sessions.WithOptions().Interceptor(interceptor).OpenSession()) { Assert.AreEqual(0, interceptor.afterTransactionBeginCalled); Assert.AreEqual(0, interceptor.beforeTransactionCompletionCalled); @@ -33,7 +33,7 @@ public void AfterBegin() { var interceptor = new RecordingInterceptor(); using (new TransactionScope()) - using (sessions.OpenSession(interceptor)) + using (sessions.WithOptions().Interceptor(interceptor).OpenSession()) { Assert.AreEqual(1, interceptor.afterTransactionBeginCalled); Assert.AreEqual(0, interceptor.beforeTransactionCompletionCalled); @@ -48,7 +48,7 @@ public void Complete() ISession session; using(var scope = new TransactionScope()) { - session = sessions.OpenSession(interceptor); + session = sessions.WithOptions().Interceptor(interceptor).OpenSession(); scope.Complete(); } session.Dispose(); @@ -62,7 +62,7 @@ public void Rollback() { var interceptor = new RecordingInterceptor(); using (new TransactionScope()) - using (sessions.OpenSession(interceptor)) + using (sessions.WithOptions().Interceptor(interceptor).OpenSession()) { } Assert.AreEqual(0, interceptor.beforeTransactionCompletionCalled); @@ -73,7 +73,7 @@ public void Rollback() public void TwoTransactionScopesInsideOneSession() { var interceptor = new RecordingInterceptor(); - using (var session = sessions.OpenSession(interceptor)) + using (var session = sessions.WithOptions().Interceptor(interceptor).OpenSession()) { using (var scope = new TransactionScope()) { @@ -96,7 +96,7 @@ public void TwoTransactionScopesInsideOneSession() public void OneTransactionScopesInsideOneSession() { var interceptor = new RecordingInterceptor(); - using (var session = sessions.OpenSession(interceptor)) + using (var session = sessions.WithOptions().Interceptor(interceptor).OpenSession()) { using (var scope = new TransactionScope()) { @@ -165,17 +165,10 @@ public void ShouldNotifyAfterDistributedTransactionWithOwnConnection(bool doComm try { - try + using (s1 = sessions.WithOptions().Connection(ownConnection1).Interceptor(interceptor).OpenSession()) { - s1 = sessions.OpenSession(ownConnection1, interceptor); - s1.CreateCriteria().List(); } - finally - { - if (s1 != null) - s1.Dispose(); - } if (doCommit) tx.Complete(); diff --git a/src/NHibernate.Test/TestCase.cs b/src/NHibernate.Test/TestCase.cs index b82ba8bbad6..70660863f0c 100644 --- a/src/NHibernate.Test/TestCase.cs +++ b/src/NHibernate.Test/TestCase.cs @@ -376,7 +376,7 @@ protected virtual ISession OpenSession() protected virtual ISession OpenSession(IInterceptor sessionLocalInterceptor) { - _lastOpenedSession = sessions.OpenSession(sessionLocalInterceptor); + _lastOpenedSession = sessions.WithOptions().Interceptor(sessionLocalInterceptor).OpenSession(); _openedSessions.Add(_lastOpenedSession); return _lastOpenedSession; } diff --git a/src/NHibernate.Test/TransactionTest/TransactionNotificationFixture.cs b/src/NHibernate.Test/TransactionTest/TransactionNotificationFixture.cs index 89b1756b116..0fd9a5d3624 100644 --- a/src/NHibernate.Test/TransactionTest/TransactionNotificationFixture.cs +++ b/src/NHibernate.Test/TransactionTest/TransactionNotificationFixture.cs @@ -17,7 +17,7 @@ protected override IList Mappings public void NoTransaction() { var interceptor = new RecordingInterceptor(); - using (sessions.OpenSession(interceptor)) + using (sessions.WithOptions().Interceptor(interceptor).OpenSession()) { Assert.That(interceptor.afterTransactionBeginCalled, Is.EqualTo(0)); Assert.That(interceptor.beforeTransactionCompletionCalled, Is.EqualTo(0)); @@ -29,7 +29,7 @@ public void NoTransaction() public void AfterBegin() { var interceptor = new RecordingInterceptor(); - using (ISession session = sessions.OpenSession(interceptor)) + using (var session = sessions.WithOptions().Interceptor(interceptor).OpenSession()) using (session.BeginTransaction()) { Assert.That(interceptor.afterTransactionBeginCalled, Is.EqualTo(1)); @@ -42,7 +42,7 @@ public void AfterBegin() public void Commit() { var interceptor = new RecordingInterceptor(); - using (ISession session = sessions.OpenSession(interceptor)) + using (var session = sessions.WithOptions().Interceptor(interceptor).OpenSession()) { ITransaction tx = session.BeginTransaction(); tx.Commit(); @@ -56,7 +56,7 @@ public void Commit() public void Rollback() { var interceptor = new RecordingInterceptor(); - using (ISession session = sessions.OpenSession(interceptor)) + using (var session = sessions.WithOptions().Interceptor(interceptor).OpenSession()) { ITransaction tx = session.BeginTransaction(); tx.Rollback(); @@ -98,7 +98,7 @@ public void ShouldNotifyAfterTransactionWithOwnConnection(bool usePrematureClose using (var ownConnection = sessions.ConnectionProvider.GetConnection()) { - using (s = sessions.OpenSession(ownConnection, interceptor)) + using (s = sessions.WithOptions().Connection(ownConnection).Interceptor(interceptor).OpenSession()) using (s.BeginTransaction()) { s.CreateCriteria().List(); diff --git a/src/NHibernate/Cfg/Configuration.cs b/src/NHibernate/Cfg/Configuration.cs index 1c0db70fb95..74edb8ce26f 100644 --- a/src/NHibernate/Cfg/Configuration.cs +++ b/src/NHibernate/Cfg/Configuration.cs @@ -172,7 +172,7 @@ protected void Reset() secondPasses = new List(); propertyReferences = new List(); FilterDefinitions = new Dictionary(); - interceptor = emptyInterceptor; + interceptor = EmptyInterceptor.Instance; properties = Environment.Properties; auxiliaryDatabaseObjects = new List(); SqlFunctions = new Dictionary(); @@ -184,6 +184,7 @@ protected void Reset() columnNameBindingPerTable = new Dictionary(); filtersSecondPasses = new Queue(); } + [Serializable] private class Mapping : IMapping { @@ -1216,8 +1217,7 @@ public EventListeners EventListeners { get { return eventListeners; } } - - private static readonly IInterceptor emptyInterceptor = new EmptyInterceptor(); + private string defaultAssembly; private string defaultNamespace; diff --git a/src/NHibernate/Cfg/Environment.cs b/src/NHibernate/Cfg/Environment.cs index 9ad4b8229cb..a17f7108627 100644 --- a/src/NHibernate/Cfg/Environment.cs +++ b/src/NHibernate/Cfg/Environment.cs @@ -185,14 +185,6 @@ public static string Version /// This may need to be set to 3 if you are using the OdbcDriver with MS SQL Server 2008+. /// public const string OdbcDateTimeScale = "odbc.explicit_datetime_scale"; - - /// - /// If this setting is set to false, exceptions in IInterceptor.BeforeTransactionCompletion bubble to the caller of ITransaction.Commit and abort the commit. - /// If this setting is set to true, exceptions in IInterceptor.BeforeTransactionCompletion are ignored and the commit is performed. - /// The default setting is false. - /// - [Obsolete("This setting is likely to be removed in a future version of NHibernate. The workaround is to catch all exceptions in the IInterceptor implementation.")] - public const string InterceptorsBeforeTransactionCompletionIgnoreExceptions = "interceptors.beforetransactioncompletion_ignore_exceptions"; private static readonly Dictionary GlobalProperties; diff --git a/src/NHibernate/Cfg/MappingSchema/Hbm.generated.cs b/src/NHibernate/Cfg/MappingSchema/Hbm.generated.cs index 3ac0ddb71be..e9b347bfc8d 100644 --- a/src/NHibernate/Cfg/MappingSchema/Hbm.generated.cs +++ b/src/NHibernate/Cfg/MappingSchema/Hbm.generated.cs @@ -4294,14 +4294,18 @@ public enum HbmFlushMode { /// [System.Xml.Serialization.XmlEnumAttribute("auto")] Auto, - + /// - [System.Xml.Serialization.XmlEnumAttribute("never")] - Never, - + [System.Xml.Serialization.XmlEnumAttribute("manual")] + Manual, + /// [System.Xml.Serialization.XmlEnumAttribute("always")] Always, + + /// + [System.Xml.Serialization.XmlEnumAttribute("never")] + Never, } /// diff --git a/src/NHibernate/Cfg/Settings.cs b/src/NHibernate/Cfg/Settings.cs index 0df168fb773..40a5a6eea4e 100644 --- a/src/NHibernate/Cfg/Settings.cs +++ b/src/NHibernate/Cfg/Settings.cs @@ -81,6 +81,8 @@ public Settings() public bool IsIdentifierRollbackEnabled { get; internal set; } + // Since v5 + [Obsolete("Please use DefaultFlushMode instead.")] public bool IsFlushBeforeCompletionEnabled { get; internal set; } public bool IsAutoCloseSessionEnabled { get; internal set; } @@ -130,9 +132,6 @@ public Settings() /// public ILinqToHqlGeneratorsRegistry LinqToHqlGeneratorsRegistry { get; internal set; } - [Obsolete("This setting is likely to be removed in a future version of NHibernate. The workaround is to catch all exceptions in the IInterceptor implementation.")] - public bool IsInterceptorsBeforeTransactionCompletionIgnoreExceptionsEnabled { get; internal set; } - public IQueryModelRewriterFactory QueryModelRewriterFactory { get; internal set; } #endregion diff --git a/src/NHibernate/Cfg/SettingsFactory.cs b/src/NHibernate/Cfg/SettingsFactory.cs index f7832c99ac8..be76a5c5e9e 100644 --- a/src/NHibernate/Cfg/SettingsFactory.cs +++ b/src/NHibernate/Cfg/SettingsFactory.cs @@ -281,12 +281,6 @@ public Settings BuildSettings(IDictionary properties) bool namedQueryChecking = PropertiesHelper.GetBoolean(Environment.QueryStartupChecking, properties, true); log.Info("Named query checking : " + EnabledDisabled(namedQueryChecking)); settings.IsNamedQueryStartupCheckingEnabled = namedQueryChecking; - -#pragma warning disable 618 // Disable warning for use of obsolete symbols. - var interceptorsBeforeTransactionCompletionIgnoreExceptions = PropertiesHelper.GetBoolean(Environment.InterceptorsBeforeTransactionCompletionIgnoreExceptions, properties, false); - log.Info("Ignoring exceptions in BeforeTransactionCompletion : " + EnabledDisabled(interceptorsBeforeTransactionCompletionIgnoreExceptions)); - settings.IsInterceptorsBeforeTransactionCompletionIgnoreExceptionsEnabled = interceptorsBeforeTransactionCompletionIgnoreExceptions; -#pragma warning restore 618 // Not ported - settings.StatementFetchSize = statementFetchSize; // Not ported - ScrollableResultSetsEnabled diff --git a/src/NHibernate/Cfg/XmlHbmBinding/FlushModeConverter.cs b/src/NHibernate/Cfg/XmlHbmBinding/FlushModeConverter.cs index abdd53f62b9..8a85b3af064 100644 --- a/src/NHibernate/Cfg/XmlHbmBinding/FlushModeConverter.cs +++ b/src/NHibernate/Cfg/XmlHbmBinding/FlushModeConverter.cs @@ -26,8 +26,9 @@ private static FlushMode GetFlushMode(bool flushModeSpecified, HbmFlushMode flus case HbmFlushMode.Auto: return FlushMode.Auto; + case HbmFlushMode.Manual: case HbmFlushMode.Never: - return FlushMode.Never; + return FlushMode.Manual; case HbmFlushMode.Always: return FlushMode.Always; diff --git a/src/NHibernate/EmptyInterceptor.cs b/src/NHibernate/EmptyInterceptor.cs index e7d41890f34..62fd6583bea 100644 --- a/src/NHibernate/EmptyInterceptor.cs +++ b/src/NHibernate/EmptyInterceptor.cs @@ -5,9 +5,17 @@ namespace NHibernate { + /// + /// An interceptor that does nothing. May be used as a base class for application-defined custom interceptors. + /// [Serializable] public class EmptyInterceptor : IInterceptor { + /// + /// The singleton reference. + /// + public static readonly EmptyInterceptor Instance = new EmptyInterceptor(); + public virtual void OnDelete(object entity, object id, object[] state, string[] propertyNames, IType[] types) { } @@ -25,7 +33,7 @@ public virtual void OnCollectionUpdate(object collection, object key) } public virtual bool OnFlushDirty(object entity, object id, object[] currentState, object[] previousState, - string[] propertyNames, IType[] types) + string[] propertyNames, IType[] types) { return false; } @@ -69,7 +77,7 @@ public virtual object GetEntity(string entityName, object id) } public virtual int[] FindDirty(object entity, object id, object[] currentState, object[] previousState, - string[] propertyNames, IType[] types) + string[] propertyNames, IType[] types) { return null; } diff --git a/src/NHibernate/FlushMode.cs b/src/NHibernate/FlushMode.cs index 1503cb4b4b3..bfc29c2bb72 100644 --- a/src/NHibernate/FlushMode.cs +++ b/src/NHibernate/FlushMode.cs @@ -22,7 +22,15 @@ public enum FlushMode /// called by the application. This mode is very efficient for read only /// transactions /// - Never = 0, + Manual = 0, + // Obsolete in v5. + /// + /// The ISession is never flushed unless Flush() is explicitly + /// called by the application. This mode is very efficient for read only + /// transactions + /// + [Obsolete("Please use Manual instead.")] + Never = Manual, /// /// The ISession is flushed when Transaction.Commit() is called /// @@ -32,7 +40,7 @@ public enum FlushMode /// ensure that queries never return stale state. This is the default flush mode. /// Auto = 10, - /// + /// /// The is flushed before every query. This is /// almost always unnecessary and inefficient. /// diff --git a/src/NHibernate/ISession.cs b/src/NHibernate/ISession.cs index 7924d37c389..bc303bec981 100644 --- a/src/NHibernate/ISession.cs +++ b/src/NHibernate/ISession.cs @@ -73,6 +73,13 @@ namespace NHibernate /// public interface ISession : IDisposable { + /// + /// Obtain a builder with the ability to grab certain information from + /// this session. The built ISession will require its own flushes and disposal. + /// + /// The session builder. + ISharedSessionBuilder SessionWithOptions(); + /// /// Force the ISession to flush. /// @@ -929,12 +936,16 @@ public interface ISession : IDisposable /// Get the statistics for this session. ISessionStatistics Statistics { get; } - /// - /// Starts a new Session. This secondary Session inherits the connection, transaction, - /// and other context information from the primary Session. It doesn't need to be flushed - /// or closed by the developer. - /// - /// The new session - ISession GetChildSession(); + // Obsolete since v5. + /// + /// Starts a new Session with the given entity mode in effect. This secondary + /// Session inherits the connection, transaction, and other context + /// information from the primary Session. It has to be flushed + /// or disposed by the developer since v5. + /// + /// Ignored. + /// The new session. + [Obsolete("Please use SessionWithOptions instead. Now requires to be flushed and disposed of.")] + ISession GetSession(EntityMode entityMode); } } diff --git a/src/NHibernate/ISessionBuilder.cs b/src/NHibernate/ISessionBuilder.cs new file mode 100644 index 00000000000..52937ddd08c --- /dev/null +++ b/src/NHibernate/ISessionBuilder.cs @@ -0,0 +1,75 @@ +using System.Data.Common; +using NHibernate.Connection; + +namespace NHibernate +{ + // 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 { } + + /// + /// Represents a consolidation of all session creation options into a builder style delegate. + /// + public interface ISessionBuilder where T : ISessionBuilder + { + /// + /// Opens a session with the specified options. + /// + /// The session. + ISession OpenSession(); + + /// + /// Adds a specific interceptor to the session options. + /// + /// The interceptor to use. + /// , for method chaining. + T Interceptor(IInterceptor interceptor); + + /// + /// Signifies that no should be used. + /// + /// , for method chaining. + /// + /// By default the associated with the is + /// passed to the whenever we open one without the user having specified a + /// specific interceptor to use. + /// + T NoInterceptor(); + + /// + /// Adds a specific connection to the session options. + /// + /// The connection to use. + /// , for method chaining. + /// + /// Note that the second-level cache will be disabled if you + /// supply a ADO.NET connection. NHibernate will not be able to track + /// any statements you might have executed in the same transaction. + /// Consider implementing your own . + /// + T Connection(DbConnection connection); + + /// + /// Use a specific connection release mode for these session options. + /// + /// The connection release mode to use. + /// , for method chaining. + T ConnectionReleaseMode(ConnectionReleaseMode connectionReleaseMode); + + /// + /// Should the session be automatically closed after transaction completion? + /// + /// Should the session be automatically closed. + /// , for method chaining. + T AutoClose(bool autoClose); + + /// + /// Specify the initial FlushMode to use for the opened Session. + /// + /// The initial FlushMode to use for the opened Session. + /// , for method chaining. + T FlushMode(FlushMode flushMode); + } +} \ No newline at end of file diff --git a/src/NHibernate/ISessionFactory.cs b/src/NHibernate/ISessionFactory.cs index 08e4dcfc301..587f7372214 100644 --- a/src/NHibernate/ISessionFactory.cs +++ b/src/NHibernate/ISessionFactory.cs @@ -26,9 +26,16 @@ namespace NHibernate public interface ISessionFactory : IDisposable { /// - /// Open a ISession on the given connection + /// Obtain a builder. /// - /// A connection provided by the application + /// The session builder. + ISessionBuilder WithOptions(); + + // Obsolete in v5. + /// + /// Open a on the given connection + /// + /// A connection provided by the application /// A session /// /// Note that the second-level cache will be disabled if you @@ -36,35 +43,59 @@ public interface ISessionFactory : IDisposable /// any statements you might have executed in the same transaction. /// Consider implementing your own . /// - ISession OpenSession(DbConnection conn); + [Obsolete("Please use WithOptions instead.")] + ISession OpenSession(DbConnection connection); + // Obsolete in v5. /// - /// Create database connection and open a ISession on it, specifying an interceptor + /// Create database connection and open a on it, specifying an interceptor /// /// A session-scoped interceptor - /// A session + /// A session. + [Obsolete("Please use WithOptions instead.")] ISession OpenSession(IInterceptor sessionLocalInterceptor); + // Obsolete in v5. /// - /// Open a ISession on the given connection, specifying an interceptor + /// Open a on the given connection, specifying an interceptor /// /// A connection provided by the application /// A session-scoped interceptor - /// A session + /// A session. /// /// Note that the second-level cache will be disabled if you /// supply a ADO.NET connection. NHibernate will not be able to track /// any statements you might have executed in the same transaction. /// Consider implementing your own . /// + [Obsolete("Please use WithOptions instead.")] ISession OpenSession(DbConnection conn, IInterceptor sessionLocalInterceptor); /// - /// Create a database connection and open a ISession on it + /// Create a database connection and open a on it /// - /// + /// A session. ISession OpenSession(); + /// + /// Obtain a builder. + /// + /// The session builder. + IStatelessSessionBuilder WithStatelessOptions(); + + /// + /// Get a new . + /// + /// A stateless session + IStatelessSession OpenStatelessSession(); + + /// + /// Get a new for the given ADO.NET connection. + /// + /// A connection provided by the application + /// A stateless session + IStatelessSession OpenStatelessSession(DbConnection connection); + /// /// Get the associated with the given entity class /// @@ -167,12 +198,6 @@ public interface ISessionFactory : IDisposable /// void EvictQueries(string cacheRegion); - /// Get a new stateless session. - IStatelessSession OpenStatelessSession(); - - /// Get a new stateless session for the given ADO.NET connection. - IStatelessSession OpenStatelessSession(DbConnection connection); - /// /// Obtain the definition of a filter by name. /// diff --git a/src/NHibernate/ISharedSessionBuilder.cs b/src/NHibernate/ISharedSessionBuilder.cs new file mode 100644 index 00000000000..9bbbddbc6cd --- /dev/null +++ b/src/NHibernate/ISharedSessionBuilder.cs @@ -0,0 +1,40 @@ +namespace NHibernate +{ + // NH different implementation: will not try to support covariant return type for specializations + // until it is needed. + /// + /// Specialized with access to stuff from another session. + /// + public interface ISharedSessionBuilder : ISessionBuilder + { + /// + /// Signifies that the connection from the original session should be used to create the new session. + /// + /// , for method chaining. + ISharedSessionBuilder Connection(); + + /// + /// Signifies the interceptor from the original session should be used to create the new session. + /// + /// , for method chaining. + ISharedSessionBuilder Interceptor(); + + /// + /// Signifies that the connection release mode from the original session should be used to create the new session. + /// + /// , for method chaining. + ISharedSessionBuilder ConnectionReleaseMode(); + + /// + /// Signifies that the FlushMode from the original session should be used to create the new session. + /// + /// , for method chaining. + ISharedSessionBuilder FlushMode(); + + /// + /// Signifies that the autoClose flag from the original session should be used to create the new session. + /// + /// , for method chaining. + ISharedSessionBuilder AutoClose(); + } +} \ No newline at end of file diff --git a/src/NHibernate/IStatelessSessionBuilder.cs b/src/NHibernate/IStatelessSessionBuilder.cs new file mode 100644 index 00000000000..25cbca41692 --- /dev/null +++ b/src/NHibernate/IStatelessSessionBuilder.cs @@ -0,0 +1,34 @@ +using System.Data.Common; +using NHibernate.Connection; + +namespace NHibernate +{ + // NH different implementation: will not try to support covariant return type for specializations + // until it is needed. + /// + /// Represents a consolidation of all stateless session creation options into a builder style delegate. + /// + public interface IStatelessSessionBuilder + { + /// + /// Opens a session with the specified options. + /// + /// The session. + IStatelessSession OpenStatelessSession(); + + /// + /// Adds a specific connection to the session options. + /// + /// The connection to use. + /// , for method chaining. + /// + /// Note that the second-level cache will be disabled if you + /// supply a ADO.NET connection. NHibernate will not be able to track + /// any statements you might have executed in the same transaction. + /// Consider implementing your own . + /// + IStatelessSessionBuilder Connection(DbConnection connection); + + // 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 3cd45907640..9c6e844fa62 100644 --- a/src/NHibernate/Impl/AbstractSessionImpl.cs +++ b/src/NHibernate/Impl/AbstractSessionImpl.cs @@ -19,16 +19,14 @@ namespace NHibernate.Impl { - - /// Functionality common to stateless and stateful sessions [Serializable] public abstract class AbstractSessionImpl : ISessionImplementor { [NonSerialized] - private ISessionFactoryImplementor factory; + private ISessionFactoryImplementor _factory; + private FlushMode _flushMode; - private readonly Guid sessionId = Guid.NewGuid(); private bool closed; public ITransactionContext TransactionContext @@ -40,22 +38,16 @@ public ITransactionContext TransactionContext private static readonly IInternalLogger logger = LoggerProvider.LoggerFor(typeof(AbstractSessionImpl)); - public Guid SessionId - { - get { return sessionId; } - } + public Guid SessionId { get; } = Guid.NewGuid(); internal AbstractSessionImpl() { } - protected internal AbstractSessionImpl(ISessionFactoryImplementor factory) + protected internal AbstractSessionImpl(ISessionFactoryImplementor factory, ISessionCreationOptions options) { - this.factory = factory; - } - - protected internal AbstractSessionImpl(ISessionFactoryImplementor factory, Guid sessionId) - : this(factory) - { - this.sessionId = sessionId; + _factory = factory; + Timestamp = factory.Settings.CacheProvider.NextTimestamp(); + _flushMode = options.InitialSessionFlushMode; + Interceptor = options.SessionInterceptor ?? EmptyInterceptor.Instance; } #region ISessionImplementor Members @@ -71,7 +63,6 @@ public void Initialize() public abstract void InitializeCollection(IPersistentCollection collection, bool writing); public abstract object InternalLoad(string entityName, object id, bool eager, bool isNullable); public abstract object ImmediateLoad(string entityName, object id); - public abstract long Timestamp { get; } public EntityKey GenerateEntityKey(object id, IEntityPersister persister) { @@ -85,8 +76,8 @@ public CacheKey GenerateCacheKey(object id, IType type, string entityOrRoleName) public ISessionFactoryImplementor Factory { - get { return factory; } - protected set { factory = value; } + get => _factory; + protected set => _factory = value; } public abstract IBatcher Batcher { get; } @@ -112,9 +103,10 @@ public virtual IList List(string query, QueryParameters queryParameters) public virtual IList List(IQueryExpression queryExpression, QueryParameters parameters) { - var results = (IList) typeof (List<>).MakeGenericType(queryExpression.Type) - .GetConstructor(System.Type.EmptyTypes) - .Invoke(null); + var results = (IList)typeof(List<>) + .MakeGenericType(queryExpression.Type) + .GetConstructor(System.Type.EmptyTypes) + .Invoke(null); List(queryExpression, parameters, results); return results; } @@ -142,7 +134,7 @@ public virtual IList List(CriteriaImpl criteria) } public abstract void List(CriteriaImpl criteria, IList results); - + public virtual IList List(CriteriaImpl criteria) { using (new SessionIdLoggingContext(SessionId)) @@ -218,13 +210,13 @@ public virtual IQuery GetNamedSQLQuery(string name) using (new SessionIdLoggingContext(SessionId)) { CheckAndUpdateSessionStatus(); - NamedSQLQueryDefinition nsqlqd = factory.GetNamedSQLQuery(name); + var nsqlqd = _factory.GetNamedSQLQuery(name); if (nsqlqd == null) { throw new MappingException("Named SQL query not known: " + name); } - IQuery query = new SqlQueryImpl(nsqlqd, this, - factory.QueryPlanCache.GetSQLParameterMetadata(nsqlqd.QueryString)); + var query = new SqlQueryImpl(nsqlqd, this, + _factory.QueryPlanCache.GetSQLParameterMetadata(nsqlqd.QueryString)); query.SetComment("named native SQL query " + name); InitQuery(query, nsqlqd); return query; @@ -236,9 +228,8 @@ public virtual IQueryTranslator[] GetQueries(string query, bool scalar) { return GetQueries(query.ToQueryExpression(), scalar); } - + public abstract IQueryTranslator[] GetQueries(IQueryExpression query, bool scalar); - public abstract IInterceptor Interceptor { get; } public abstract EventListeners Listeners { get; } public abstract int DontFlushFromFind { get; } public abstract ConnectionManager ConnectionManager { get; } @@ -248,7 +239,6 @@ public virtual IQueryTranslator[] GetQueries(string query, bool scalar) public abstract CacheMode CacheMode { get; set; } public abstract bool IsOpen { get; } public abstract bool IsConnected { get; } - public abstract FlushMode FlushMode { get; set; } public abstract string FetchProfile { get; set; } public abstract string BestGuessEntityName(object entity); public abstract string GuessEntityName(object entity); @@ -257,28 +247,36 @@ public virtual IQueryTranslator[] GetQueries(string query, bool scalar) public abstract FutureCriteriaBatch FutureCriteriaBatch { get; protected internal set; } public abstract FutureQueryBatch FutureQueryBatch { get; protected internal set; } + public virtual IInterceptor Interceptor { get; protected set; } + + public virtual FlushMode FlushMode + { + get => _flushMode; + set => _flushMode = value; + } + public virtual IQuery GetNamedQuery(string queryName) { using (new SessionIdLoggingContext(SessionId)) { CheckAndUpdateSessionStatus(); - NamedQueryDefinition nqd = factory.GetNamedQuery(queryName); + var nqd = _factory.GetNamedQuery(queryName); IQuery query; if (nqd != null) { - string queryString = nqd.QueryString; + var queryString = nqd.QueryString; query = new QueryImpl(queryString, nqd.FlushMode, this, GetHQLQueryPlan(queryString.ToQueryExpression(), false).ParameterMetadata); query.SetComment("named HQL query " + queryName); } else { - NamedSQLQueryDefinition nsqlqd = factory.GetNamedSQLQuery(queryName); + var nsqlqd = _factory.GetNamedSQLQuery(queryName); if (nsqlqd == null) { throw new MappingException("Named query not known: " + queryName); } query = new SqlQueryImpl(nsqlqd, this, - factory.QueryPlanCache.GetSQLParameterMetadata(nsqlqd.QueryString)); + _factory.QueryPlanCache.GetSQLParameterMetadata(nsqlqd.QueryString)); query.SetComment("named native SQL query " + queryName); nqd = nsqlqd; } @@ -287,6 +285,8 @@ public virtual IQuery GetNamedQuery(string queryName) } } + public virtual long Timestamp { get; protected set; } + public bool IsClosed { get { return closed; } @@ -302,7 +302,7 @@ protected internal virtual void ErrorIfClosed() { if (IsClosed || IsAlreadyDisposed) { - throw new ObjectDisposedException("ISession", "Session is closed!"); + throw new ObjectDisposedException(nameof(ISession), "Session is closed!"); } } @@ -387,7 +387,7 @@ public virtual ISQLQuery CreateSQLQuery(string sql) using (new SessionIdLoggingContext(SessionId)) { CheckAndUpdateSessionStatus(); - SqlQueryImpl query = new SqlQueryImpl(sql, this, factory.QueryPlanCache.GetSQLParameterMetadata(sql)); + var query = new SqlQueryImpl(sql, this, _factory.QueryPlanCache.GetSQLParameterMetadata(sql)); query.SetComment("dynamic native SQL query"); return query; } @@ -403,7 +403,7 @@ protected internal virtual IQueryExpressionPlan GetHQLQueryPlan(IQueryExpression { using (new SessionIdLoggingContext(SessionId)) { - return factory.QueryPlanCache.GetHQLQueryPlan(queryExpression, shallow, EnabledFilters); + return _factory.QueryPlanCache.GetHQLQueryPlan(queryExpression, shallow, EnabledFilters); } } @@ -411,7 +411,7 @@ protected internal virtual NativeSQLQueryPlan GetNativeSQLQueryPlan(NativeSQLQue { using (new SessionIdLoggingContext(SessionId)) { - return factory.QueryPlanCache.GetNativeSQLQueryPlan(spec); + return _factory.QueryPlanCache.GetNativeSQLQueryPlan(spec); } } @@ -419,7 +419,7 @@ protected Exception Convert(Exception sqlException, string message) { using (new SessionIdLoggingContext(SessionId)) { - return ADOExceptionHelper.Convert(factory.SQLExceptionConverter, sqlException, message); + return ADOExceptionHelper.Convert(_factory.SQLExceptionConverter, sqlException, message); } } @@ -437,7 +437,7 @@ protected void AfterOperation(bool success) protected void EnlistInAmbientTransactionIfNeeded() { - factory.TransactionFactory.EnlistInDistributedTransactionIfNeeded(this); + _factory.TransactionFactory.EnlistInDistributedTransactionIfNeeded(this); } internal IOuterJoinLoadable GetOuterJoinLoadable(string entityName) diff --git a/src/NHibernate/Impl/ISessionCreationOptions.cs b/src/NHibernate/Impl/ISessionCreationOptions.cs new file mode 100644 index 00000000000..88ea471aa67 --- /dev/null +++ b/src/NHibernate/Impl/ISessionCreationOptions.cs @@ -0,0 +1,24 @@ +using System.Data.Common; + +namespace NHibernate.Impl +{ + /// + /// Options for session creation. + /// + /// + public interface ISessionCreationOptions + { + // NH note: naming "adjusted" for converting Java methods to properties while avoiding conflicts with + // ISessionBuilder. + FlushMode InitialSessionFlushMode { get; } + + bool ShouldAutoClose { get; } + + DbConnection UserSuppliedConnection { get; } + + IInterceptor SessionInterceptor { get; } + + // Todo: port PhysicalConnectionHandlingMode + ConnectionReleaseMode SessionConnectionReleaseMode { get; } + } +} \ No newline at end of file diff --git a/src/NHibernate/Impl/ISharedSessionCreationOptions.cs b/src/NHibernate/Impl/ISharedSessionCreationOptions.cs new file mode 100644 index 00000000000..a0fd77451f0 --- /dev/null +++ b/src/NHibernate/Impl/ISharedSessionCreationOptions.cs @@ -0,0 +1,18 @@ +using NHibernate.AdoNet; + +namespace NHibernate.Impl +{ + /// + /// An extension of SessionCreationOptions for cases where the Session to be created shares + /// some part of the "transaction context" of another Session. + /// + /// + public interface ISharedSessionCreationOptions : ISessionCreationOptions + { + // NH note: naming "adjusted" for converting Java methods to properties while avoiding conflicts with + // ISessionBuilder. + bool IsTransactionCoordinatorShared { get; } + // NH different implementation: need to port Hibernate transaction management. + ConnectionManager ConnectionManager { get; } + } +} \ No newline at end of file diff --git a/src/NHibernate/Impl/SessionFactoryImpl.cs b/src/NHibernate/Impl/SessionFactoryImpl.cs index feaadece7b3..9b1459ae687 100644 --- a/src/NHibernate/Impl/SessionFactoryImpl.cs +++ b/src/NHibernate/Impl/SessionFactoryImpl.cs @@ -66,11 +66,11 @@ namespace NHibernate.Impl /// , but also highly concurrent. Synchronization must be used extremely sparingly. /// /// - /// - /// - /// - /// - /// + /// + /// + /// + /// + /// [Serializable] public sealed class SessionFactoryImpl : ISessionFactoryImplementor, IObjectReference { @@ -460,45 +460,72 @@ public object GetRealObject(StreamingContext context) #region ISessionFactoryImplementor Members + public ISessionBuilder WithOptions() + { + return new SessionBuilderImpl(this); + } + public ISession OpenSession() { - return OpenSession(interceptor); + return WithOptions().OpenSession(); } + // Obsolete since v5 + [Obsolete("Please use WithOptions instead.")] public ISession OpenSession(DbConnection connection) { - return OpenSession(connection, interceptor); + return WithOptions() + .Connection(connection) + .OpenSession(); } + // Obsolete since v5 + [Obsolete("Please use WithOptions instead.")] public ISession OpenSession(DbConnection connection, IInterceptor sessionLocalInterceptor) { - if (sessionLocalInterceptor == null) - { - throw new ArgumentNullException("sessionLocalInterceptor"); - } - return OpenSession(connection, false, long.MinValue, sessionLocalInterceptor); + return WithOptions() + .Connection(connection) + .Interceptor(sessionLocalInterceptor) + .OpenSession(); } + // Obsolete since v5 + [Obsolete("Please use WithOptions instead.")] public ISession OpenSession(IInterceptor sessionLocalInterceptor) { - if (sessionLocalInterceptor == null) - { - throw new ArgumentNullException("sessionLocalInterceptor"); - } - long timestamp = settings.CacheProvider.NextTimestamp(); - return OpenSession(null, true, timestamp, sessionLocalInterceptor); + return WithOptions() + .Interceptor(sessionLocalInterceptor) + .OpenSession(); } + // Obsolete since v5 + [Obsolete("Please use WithOptions instead.")] public ISession OpenSession(DbConnection connection, bool flushBeforeCompletionEnabled, bool autoCloseSessionEnabled, - ConnectionReleaseMode connectionReleaseMode) + ConnectionReleaseMode connectionReleaseMode) { -#pragma warning disable 618 - var isInterceptorsBeforeTransactionCompletionIgnoreExceptionsEnabled = settings.IsInterceptorsBeforeTransactionCompletionIgnoreExceptionsEnabled; -#pragma warning restore 618 + return WithOptions() + .Connection(connection) + .Interceptor(interceptor) + .AutoClose(autoCloseSessionEnabled) + .ConnectionReleaseMode(connectionReleaseMode) + .OpenSession(); + } - return - new SessionImpl(connection, this, true, settings.CacheProvider.NextTimestamp(), interceptor, flushBeforeCompletionEnabled, autoCloseSessionEnabled, - isInterceptorsBeforeTransactionCompletionIgnoreExceptionsEnabled, connectionReleaseMode, settings.DefaultFlushMode); + public IStatelessSessionBuilder WithStatelessOptions() + { + return new StatelessSessionBuilderImpl(this); + } + + public IStatelessSession OpenStatelessSession() + { + return WithStatelessOptions().OpenStatelessSession(); + } + + public IStatelessSession OpenStatelessSession(DbConnection connection) + { + return WithStatelessOptions() + .Connection(connection) + .OpenStatelessSession(); } public IEntityPersister GetEntityPersister(string entityName) @@ -1085,18 +1112,6 @@ public ISession GetCurrentSession() return currentSessionContext.CurrentSession(); } - /// Get a new stateless session. - public IStatelessSession OpenStatelessSession() - { - return new StatelessSessionImpl(null, this); - } - - /// Get a new stateless session for the given ADO.NET connection. - public IStatelessSession OpenStatelessSession(DbConnection connection) - { - return new StatelessSessionImpl(connection, this); - } - /// Get the statistics for this session factory public IStatistics Statistics { @@ -1201,24 +1216,6 @@ private IDictionary CheckNamedQueries() return errors; } - private SessionImpl OpenSession(DbConnection connection, bool autoClose, long timestamp, IInterceptor sessionLocalInterceptor) - { -#pragma warning disable 618 - var isInterceptorsBeforeTransactionCompletionIgnoreExceptionsEnabled = settings.IsInterceptorsBeforeTransactionCompletionIgnoreExceptionsEnabled; -#pragma warning restore 618 - - SessionImpl session = new SessionImpl(connection, this, autoClose, timestamp, sessionLocalInterceptor ?? interceptor, settings.IsFlushBeforeCompletionEnabled, - settings.IsAutoCloseSessionEnabled, isInterceptorsBeforeTransactionCompletionIgnoreExceptionsEnabled, - settings.ConnectionReleaseMode, settings.DefaultFlushMode); - - if (sessionLocalInterceptor != null) - { - // NH specific feature - sessionLocalInterceptor.SetSession(session); - } - return session; - } - private ICurrentSessionContext BuildCurrentSessionContext() { string impl = PropertiesHelper.GetString(Environment.CurrentSessionContextClass, properties, null); @@ -1270,5 +1267,146 @@ public string Uuid } #endregion + + // NH specific: implementing return type covariance with interface is a mess in .Net. + internal class SessionBuilderImpl : SessionBuilderImpl, ISessionBuilder + { + public SessionBuilderImpl(SessionFactoryImpl sessionFactory) : base(sessionFactory) + { + SetSelf(this); + } + } + + internal class SessionBuilderImpl : ISessionBuilder, ISessionCreationOptions where T : ISessionBuilder + { + // NH specific: implementing return type covariance with interface is a mess in .Net. + private T _this; + private static readonly IInternalLogger _log = LoggerProvider.LoggerFor(typeof(SessionBuilderImpl)); + + private readonly SessionFactoryImpl _sessionFactory; + private IInterceptor _interceptor; + private DbConnection _connection; + // Todo: port PhysicalConnectionHandlingMode + private ConnectionReleaseMode _connectionReleaseMode; + private FlushMode _flushMode; + private bool _autoClose; + + public SessionBuilderImpl(SessionFactoryImpl sessionFactory) + { + _sessionFactory = sessionFactory; + + // set up default builder values... + _connectionReleaseMode = sessionFactory.Settings.ConnectionReleaseMode; + _autoClose = sessionFactory.Settings.IsAutoCloseSessionEnabled; + // NH different implementation: not using Settings.IsFlushBeforeCompletionEnabled + _flushMode = sessionFactory.Settings.DefaultFlushMode; + } + + protected void SetSelf(T self) + { + _this = self; + } + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // SessionCreationOptions + + public virtual FlushMode InitialSessionFlushMode => _flushMode; + + public virtual bool ShouldAutoClose => _autoClose; + + public DbConnection UserSuppliedConnection => _connection; + + // NH different implementation: Hibernate here ignore EmptyInterceptor.Instance too, resulting + // in the "NoInterceptor" being unable to override a session factory interceptor. + public virtual IInterceptor SessionInterceptor => _interceptor ?? _sessionFactory.Interceptor; + + public virtual ConnectionReleaseMode SessionConnectionReleaseMode => _connectionReleaseMode; + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // SessionBuilder + + public virtual ISession OpenSession() + { + _log.Debug("Opening Hibernate Session."); + var session = new SessionImpl(_sessionFactory, this); + if (_interceptor != null) + { + // NH specific feature + // _interceptor may be the shared accros threads EmptyInterceptor.Instance, but that is + // not an issue, SetSession is no-op on it. + _interceptor.SetSession(session); + } + + return session; + } + + public virtual T Interceptor(IInterceptor interceptor) + { + // NH different implementation: Hibernate accepts null. + _interceptor = interceptor ?? throw new ArgumentNullException(nameof(interceptor)); + return _this; + } + + public virtual T NoInterceptor() + { + _interceptor = EmptyInterceptor.Instance; + return _this; + } + + public virtual T Connection(DbConnection connection) + { + _connection = connection; + return _this; + } + + public virtual T ConnectionReleaseMode(ConnectionReleaseMode connectionReleaseMode) + { + _connectionReleaseMode = connectionReleaseMode; + return _this; + } + + public virtual T AutoClose(bool autoClose) + { + _autoClose = autoClose; + return _this; + } + + public virtual T FlushMode(FlushMode flushMode) + { + _flushMode = flushMode; + return _this; + } + } + + // NH different implementation: will not try to support covariant return type for specializations + // until it is needed. + internal class StatelessSessionBuilderImpl : IStatelessSessionBuilder, ISessionCreationOptions + { + private readonly SessionFactoryImpl _sessionFactory; + private DbConnection _connection; + + public StatelessSessionBuilderImpl(SessionFactoryImpl sessionFactory) + { + _sessionFactory = sessionFactory; + } + + public virtual IStatelessSession OpenStatelessSession() => new StatelessSessionImpl(_sessionFactory, this); + + public IStatelessSessionBuilder Connection(DbConnection connection) + { + _connection = connection; + return this; + } + + public FlushMode InitialSessionFlushMode => FlushMode.Always; + + public bool ShouldAutoClose => false; + + public DbConnection UserSuppliedConnection => _connection; + + public IInterceptor SessionInterceptor => EmptyInterceptor.Instance; + + public ConnectionReleaseMode SessionConnectionReleaseMode => ConnectionReleaseMode.AfterTransaction; + } } } diff --git a/src/NHibernate/Impl/SessionImpl.cs b/src/NHibernate/Impl/SessionImpl.cs index fd6ba98b5f0..bc0704ceebc 100644 --- a/src/NHibernate/Impl/SessionImpl.cs +++ b/src/NHibernate/Impl/SessionImpl.cs @@ -27,11 +27,11 @@ namespace NHibernate.Impl { /// - /// Concrete implementation of an , also the central, organizing component + /// Concrete implementation of an , also the central, organizing component /// of NHibernate's internal implementation. /// /// - /// Exposes two interfaces: itself, to the application and + /// Exposes two interfaces: itself, to the application and /// to other components of NHibernate. This is where the /// hard stuff is... This class is NOT THREADSAFE. /// @@ -40,12 +40,7 @@ public sealed class SessionImpl : AbstractSessionImpl, IEventSource, ISerializab { private static readonly IInternalLogger log = LoggerProvider.LoggerFor(typeof(SessionImpl)); - private readonly long timestamp; - private CacheMode cacheMode = CacheMode.Normal; - private FlushMode flushMode = FlushMode.Auto; - - private readonly IInterceptor interceptor; [NonSerialized] private FutureCriteriaBatch futureCriteriaBatch; @@ -72,20 +67,12 @@ public sealed class SessionImpl : AbstractSessionImpl, IEventSource, ISerializab [NonSerialized] private readonly StatefulPersistenceContext persistenceContext; - [NonSerialized] - private readonly SessionImpl rootSession; - - [NonSerialized] - ISession _childSession; - - [NonSerialized] - private readonly bool flushBeforeCompletionEnabled; [NonSerialized] private readonly bool autoCloseSessionEnabled; [NonSerialized] - private readonly bool ignoreExceptionBeforeTransactionCompletion; - [NonSerialized] private readonly ConnectionReleaseMode connectionReleaseMode; + [NonSerialized] + private readonly bool _transactionCoordinatorShared; #region System.Runtime.Serialization.ISerializable Members @@ -101,8 +88,7 @@ public sealed class SessionImpl : AbstractSessionImpl, IEventSource, ISerializab /// private SessionImpl(SerializationInfo info, StreamingContext context) { - timestamp = info.GetInt64("timestamp"); - + Timestamp = info.GetInt64("timestamp"); SessionFactoryImpl fact = (SessionFactoryImpl)info.GetValue("factory", typeof(SessionFactoryImpl)); Factory = fact; listeners = fact.EventListeners; @@ -110,10 +96,10 @@ private SessionImpl(SerializationInfo info, StreamingContext context) actionQueue = (ActionQueue)info.GetValue("actionQueue", typeof(ActionQueue)); - flushMode = (FlushMode)info.GetValue("flushMode", typeof(FlushMode)); + FlushMode = (FlushMode)info.GetValue("flushMode", typeof(FlushMode)); cacheMode = (CacheMode)info.GetValue("cacheMode", typeof(CacheMode)); - interceptor = (IInterceptor)info.GetValue("interceptor", typeof(IInterceptor)); + Interceptor = (IInterceptor)info.GetValue("interceptor", typeof(IInterceptor)); enabledFilters = (IDictionary)info.GetValue("enabledFilters", typeof(Dictionary)); enabledFilterNames = (List)info.GetValue("enabledFilterNames", typeof(List)); @@ -140,15 +126,19 @@ void ISerializable.GetObjectData(SerializationInfo info, StreamingContext contex { throw new InvalidOperationException("Cannot serialize a Session while connected"); } + if (_transactionCoordinatorShared) + { + throw new InvalidOperationException("Cannot serialize a Session sharing its transaction coordinator"); + } info.AddValue("factory", Factory, typeof(SessionFactoryImpl)); info.AddValue("persistenceContext", persistenceContext, typeof(StatefulPersistenceContext)); info.AddValue("actionQueue", actionQueue, typeof(ActionQueue)); - info.AddValue("timestamp", timestamp); - info.AddValue("flushMode", flushMode); + info.AddValue("timestamp", Timestamp); + info.AddValue("flushMode", FlushMode); info.AddValue("cacheMode", cacheMode); - info.AddValue("interceptor", interceptor, typeof(IInterceptor)); + info.AddValue("interceptor", Interceptor, typeof(IInterceptor)); info.AddValue("enabledFilters", enabledFilters, typeof(IDictionary)); info.AddValue("enabledFilterNames", enabledFilterNames, typeof(List)); @@ -189,86 +179,41 @@ void IDeserializationCallback.OnDeserialization(object sender) /// Constructor used for OpenSession(...) processing, as well as construction /// of sessions for GetCurrentSession(). /// - /// The user-supplied connection to use for this session. - /// The factory from which this session was obtained - /// NOT USED - /// The timestamp for this session - /// The interceptor to be applied to this session - /// Should we auto flush before completion of transaction - /// Should we auto close after completion of transaction - /// Should we ignore exceptions in IInterceptor.BeforeTransactionCompletion - /// The mode by which we should release JDBC connections. - /// The default flush mode for this session - internal SessionImpl( - DbConnection connection, - SessionFactoryImpl factory, - bool autoclose, - long timestamp, - IInterceptor interceptor, - bool flushBeforeCompletionEnabled, - bool autoCloseSessionEnabled, - bool ignoreExceptionBeforeTransactionCompletion, - ConnectionReleaseMode connectionReleaseMode, - FlushMode defaultFlushMode) - : base(factory) - { - using (new SessionIdLoggingContext(SessionId)) - { - if (interceptor == null) - throw new AssertionFailure("The interceptor can not be null."); - - rootSession = null; - this.timestamp = timestamp; - this.interceptor = interceptor; - listeners = factory.EventListeners; + /// The factory from which this session was obtained. + /// The options of the session. + internal SessionImpl(SessionFactoryImpl factory, ISessionCreationOptions options) + : base(factory, options) + { + using (new SessionIdLoggingContext(SessionId)) + { actionQueue = new ActionQueue(this); persistenceContext = new StatefulPersistenceContext(this); - this.flushBeforeCompletionEnabled = flushBeforeCompletionEnabled; - this.autoCloseSessionEnabled = autoCloseSessionEnabled; - this.connectionReleaseMode = connectionReleaseMode; - this.ignoreExceptionBeforeTransactionCompletion = ignoreExceptionBeforeTransactionCompletion; - connectionManager = new ConnectionManager(this, connection, connectionReleaseMode, interceptor); - this.flushMode = defaultFlushMode; - if (factory.Statistics.IsStatisticsEnabled) + autoCloseSessionEnabled = options.ShouldAutoClose; + + listeners = factory.EventListeners; + connectionReleaseMode = options.SessionConnectionReleaseMode; + + if (options is ISharedSessionCreationOptions sharedOptions && sharedOptions.IsTransactionCoordinatorShared) { - factory.StatisticsImplementor.OpenSession(); + // NH specific implementation: need to port Hibernate transaction management. + _transactionCoordinatorShared = true; + if (options.UserSuppliedConnection != null) + throw new SessionException("Cannot simultaneously share transaction context and specify connection"); + connectionManager = sharedOptions.ConnectionManager; } - - if (log.IsDebugEnabled) + else { - log.DebugFormat("[session-id={0}] opened session at timestamp: {1}, for session factory: [{2}/{3}]", - SessionId, timestamp, factory.Name, factory.Uuid); + connectionManager = new ConnectionManager(this, options.UserSuppliedConnection, connectionReleaseMode, Interceptor); } - CheckAndUpdateSessionStatus(); - } - } - - /// - /// Constructor used in building "child sessions". - /// - /// The parent Session - private SessionImpl(SessionImpl parent) - : base(parent.Factory, parent.SessionId) - { - using (new SessionIdLoggingContext(SessionId)) - { - rootSession = parent; - timestamp = parent.timestamp; - connectionManager = parent.connectionManager; - interceptor = parent.interceptor; - listeners = parent.listeners; - actionQueue = new ActionQueue(this); - persistenceContext = new StatefulPersistenceContext(this); - flushBeforeCompletionEnabled = false; - autoCloseSessionEnabled = false; - connectionReleaseMode = parent.ConnectionReleaseMode; // NH different - - if (Factory.Statistics.IsStatisticsEnabled) - Factory.StatisticsImplementor.OpenSession(); + if (factory.Statistics.IsStatisticsEnabled) + { + factory.StatisticsImplementor.OpenSession(); + } - log.Debug("Opened session"); + log.DebugFormat("[session-id={0}] opened session at timestamp: {1}, for session factory: [{2}/{3}]", + SessionId, Timestamp, factory.Name, factory.Uuid); CheckAndUpdateSessionStatus(); } @@ -312,12 +257,6 @@ public override IBatcher Batcher } } - /// - public override long Timestamp - { - get { return timestamp; } - } - public ConnectionReleaseMode ConnectionReleaseMode { get { return connectionReleaseMode; } @@ -357,22 +296,7 @@ public DbConnection Close() try { - try - { - if (_childSession != null) - { - if (_childSession.IsOpen) - { - _childSession.Close(); - } - } - } - catch - { - // just ignore - } - - if (rootSession == null) + if (!_transactionCoordinatorShared) return connectionManager.Close(); else return null; @@ -381,7 +305,6 @@ public DbConnection Close() { SetClosed(); Cleanup(); - if (rootSession != null) rootSession._childSession = null; } } } @@ -404,11 +327,11 @@ public override void AfterTransactionCompletion(bool success, ITransaction tx) connectionManager.AfterTransaction(); persistenceContext.AfterTransactionCompletion(); actionQueue.AfterTransactionCompletion(success); - if (rootSession == null) + if (!_transactionCoordinatorShared) { try { - interceptor.AfterTransactionCompletion(tx); + Interceptor.AfterTransactionCompletion(tx); } catch (Exception t) { @@ -861,7 +784,7 @@ public object Instantiate(IEntityPersister persister, object id) using (new SessionIdLoggingContext(SessionId)) { ErrorIfClosed(); - object result = interceptor.Instantiate(persister.EntityName, id); + object result = Interceptor.Instantiate(persister.EntityName, id); if (result == null) { result = persister.Instantiate(id); @@ -1000,17 +923,9 @@ public void PersistOnFlush(object obj) } } - /// - public override FlushMode FlushMode - { - get { return flushMode; } - set { flushMode = value; } - } - - public bool FlushBeforeCompletionEnabled - { - get { return flushBeforeCompletionEnabled; } - } + // Obsolete in v5, and was already having no usages previously. + [Obsolete("Please use FlushMode instead.")] + public bool FlushBeforeCompletionEnabled => FlushMode >= FlushMode.Commit; public override string BestGuessEntityName(object entity) { @@ -1022,7 +937,7 @@ public override string BestGuessEntityName(object entity) ILazyInitializer initializer = proxy.HibernateLazyInitializer; // it is possible for this method to be called during flush processing, - // so make certain that we do not accidently initialize an uninitialized proxy + // so make certain that we do not accidentally initialize an uninitialized proxy if (initializer.IsUninitialized) { return initializer.PersistentClass.FullName; @@ -1051,7 +966,7 @@ public override string GuessEntityName(object entity) { using (new SessionIdLoggingContext(SessionId)) { - string entityName = interceptor.GetEntityName(entity); + string entityName = Interceptor.GetEntityName(entity); if (entityName == null) { System.Type t = entity.GetType(); @@ -1080,7 +995,7 @@ public override object GetEntityUsingInterceptor(EntityKey key) if (result == null) { - object newObject = interceptor.GetEntity(key.EntityName, key.Identifier); + object newObject = Interceptor.GetEntity(key.EntityName, key.Identifier); if (newObject != null) { Lock(newObject, LockMode.None); @@ -1384,7 +1299,7 @@ public ITransaction BeginTransaction(IsolationLevel isolationLevel) { using (new SessionIdLoggingContext(SessionId)) { - if (rootSession != null) + if (_transactionCoordinatorShared) { // Todo : should seriously consider not allowing a txn to begin from a child session // can always route the request to the root session... @@ -1400,7 +1315,7 @@ public ITransaction BeginTransaction() { using (new SessionIdLoggingContext(SessionId)) { - if (rootSession != null) + if (_transactionCoordinatorShared) { // Todo : should seriously consider not allowing a txn to begin from a child session // can always route the request to the root session... @@ -1992,7 +1907,11 @@ public override void ListCustomQuery(ICustomQuery customQuery, QueryParameters q } } - /// + public ISharedSessionBuilder SessionWithOptions() + { + return new SharedSessionBuilderImpl(this); + } + public void Clear() { using (new SessionIdLoggingContext(SessionId)) @@ -2173,7 +2092,7 @@ public override void AfterTransactionBegin(ITransaction tx) using (new SessionIdLoggingContext(SessionId)) { CheckAndUpdateSessionStatus(); - interceptor.AfterTransactionBegin(tx); + Interceptor.AfterTransactionBegin(tx); } } @@ -2183,18 +2102,17 @@ public override void BeforeTransactionCompletion(ITransaction tx) { log.Debug("before transaction completion"); actionQueue.BeforeTransactionCompletion(); - if (rootSession == null) + if (!_transactionCoordinatorShared) { try { - interceptor.BeforeTransactionCompletion(tx); + Interceptor.BeforeTransactionCompletion(tx); } catch (Exception e) { log.Error("exception in interceptor BeforeTransactionCompletion()", e); - if (ignoreExceptionBeforeTransactionCompletion == false) - throw; + throw; } } } @@ -2206,36 +2124,21 @@ public ISession SetBatchSize(int batchSize) return this; } - public ISessionImplementor GetSessionImplementation() { return this; } - public ISession GetChildSession() - { - using (new SessionIdLoggingContext(SessionId)) - { - if (rootSession != null) - { - return rootSession.GetChildSession(); - } - - CheckAndUpdateSessionStatus(); - - if (_childSession == null) - { - log.Debug("Creating child session."); - _childSession = new SessionImpl(this); - } - - return _childSession; - } - } - - public override IInterceptor Interceptor + // Obsolete since v5 + [Obsolete("Please use SessionWithOptions instead.")] + public ISession GetSession(EntityMode entityMode) { - get { return interceptor; } + return SessionWithOptions() + .Connection() + .ConnectionReleaseMode() + .FlushMode() + .Interceptor() + .OpenSession(); } /// Retrieves the configured event listeners from this event source. @@ -2606,5 +2509,56 @@ public override IEntityPersister GetEntityPersister(string entityName, object ob } } } + + // NH different implementation: will not try to support covariant return type for specializations + // of SharedSessionBuilderImpl until they need to exist. + private class SharedSessionBuilderImpl : SessionFactoryImpl.SessionBuilderImpl, + ISharedSessionBuilder, ISharedSessionCreationOptions + { + private readonly SessionImpl _session; + private bool _shareTransactionContext; + + public SharedSessionBuilderImpl(SessionImpl session) + : base((SessionFactoryImpl)session.Factory) + { + _session = session; + SetSelf(this); + } + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // SharedSessionBuilder + + public virtual ISharedSessionBuilder Interceptor() => Interceptor(_session.Interceptor); + + public virtual ISharedSessionBuilder Connection() + { + // Ensure any previously user supplied connection is removed. + base.Connection(null); + // We share the connection manager + _shareTransactionContext = true; + return this; + } + + public virtual ISharedSessionBuilder ConnectionReleaseMode() => ConnectionReleaseMode(_session.ConnectionReleaseMode); + + public virtual ISharedSessionBuilder FlushMode() => FlushMode(_session.FlushMode); + + public virtual ISharedSessionBuilder AutoClose() => AutoClose(_session.autoCloseSessionEnabled); + + // NH different implementation, avoid an error case. + public override ISharedSessionBuilder Connection(DbConnection connection) + { + _shareTransactionContext = false; + return base.Connection(connection); + } + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // SharedSessionCreationOptions + + public virtual bool IsTransactionCoordinatorShared => _shareTransactionContext; + + // NH different implementation: need to port Hibernate transaction management. + public ConnectionManager ConnectionManager => _shareTransactionContext ? _session.ConnectionManager : null; + } } } diff --git a/src/NHibernate/Impl/StatelessSessionImpl.cs b/src/NHibernate/Impl/StatelessSessionImpl.cs index 0ac8c8cfa0a..6933c4e3a82 100644 --- a/src/NHibernate/Impl/StatelessSessionImpl.cs +++ b/src/NHibernate/Impl/StatelessSessionImpl.cs @@ -16,7 +16,6 @@ using NHibernate.Id; using NHibernate.Loader.Criteria; using NHibernate.Loader.Custom; -using NHibernate.Loader.Custom.Sql; using NHibernate.Persister.Entity; using NHibernate.Proxy; using NHibernate.Type; @@ -33,14 +32,14 @@ public class StatelessSessionImpl : AbstractSessionImpl, IStatelessSession [NonSerialized] private readonly StatefulPersistenceContext temporaryPersistenceContext; - internal StatelessSessionImpl(DbConnection connection, SessionFactoryImpl factory) - : base(factory) + internal StatelessSessionImpl(SessionFactoryImpl factory, ISessionCreationOptions options) + : base(factory, options) { using (new SessionIdLoggingContext(SessionId)) { temporaryPersistenceContext = new StatefulPersistenceContext(this); - connectionManager = new ConnectionManager(this, connection, ConnectionReleaseMode.AfterTransaction, - new EmptyInterceptor()); + connectionManager = new ConnectionManager(this, options.UserSuppliedConnection, ConnectionReleaseMode.AfterTransaction, + EmptyInterceptor.Instance); if (log.IsDebugEnabled) { @@ -291,7 +290,7 @@ public override IQueryTranslator[] GetQueries(IQueryExpression query, bool scala public override IInterceptor Interceptor { - get { return new EmptyInterceptor(); } + get { return EmptyInterceptor.Instance; } } public override EventListeners Listeners diff --git a/src/NHibernate/NHibernate.csproj b/src/NHibernate/NHibernate.csproj index b52b617a9c5..80ae2fee47c 100644 --- a/src/NHibernate/NHibernate.csproj +++ b/src/NHibernate/NHibernate.csproj @@ -145,6 +145,11 @@ + + + + + diff --git a/src/NHibernate/Persister/Collection/NamedQueryCollectionInitializer.cs b/src/NHibernate/Persister/Collection/NamedQueryCollectionInitializer.cs index 69c799437c7..db6987fa9bd 100644 --- a/src/NHibernate/Persister/Collection/NamedQueryCollectionInitializer.cs +++ b/src/NHibernate/Persister/Collection/NamedQueryCollectionInitializer.cs @@ -35,7 +35,7 @@ public void Initialize(object key, ISessionImplementor session) { query.SetParameter(0, key, persister.KeyType); } - query.SetCollectionKey(key).SetFlushMode(FlushMode.Never).List(); + query.SetCollectionKey(key).SetFlushMode(FlushMode.Manual).List(); } } } \ No newline at end of file diff --git a/src/NHibernate/Persister/Entity/AbstractEntityPersister.cs b/src/NHibernate/Persister/Entity/AbstractEntityPersister.cs index 21eb794c471..38453afb6a7 100644 --- a/src/NHibernate/Persister/Entity/AbstractEntityPersister.cs +++ b/src/NHibernate/Persister/Entity/AbstractEntityPersister.cs @@ -4160,7 +4160,7 @@ private void ProcessGeneratedPropertiesWithLoader(object id, object entity, ISes query.SetOptionalId(id); query.SetOptionalEntityName(this.EntityName); query.SetOptionalObject(entity); - query.SetFlushMode(FlushMode.Never); + query.SetFlushMode(FlushMode.Manual); query.List(); } diff --git a/src/NHibernate/Persister/Entity/NamedQueryLoader.cs b/src/NHibernate/Persister/Entity/NamedQueryLoader.cs index df856a948c3..b31b99af843 100644 --- a/src/NHibernate/Persister/Entity/NamedQueryLoader.cs +++ b/src/NHibernate/Persister/Entity/NamedQueryLoader.cs @@ -40,7 +40,7 @@ public object Load(object id, object optionalObject, ISessionImplementor session query.SetOptionalId(id); query.SetOptionalEntityName(persister.EntityName); query.SetOptionalObject(optionalObject); - query.SetFlushMode(FlushMode.Never); + query.SetFlushMode(FlushMode.Manual); query.List(); // now look up the object we are really interested in! diff --git a/src/NHibernate/Transaction/AdoNetWithDistributedTransactionFactory.cs b/src/NHibernate/Transaction/AdoNetWithDistributedTransactionFactory.cs index 2290996dd6c..d2cdceb3c2c 100644 --- a/src/NHibernate/Transaction/AdoNetWithDistributedTransactionFactory.cs +++ b/src/NHibernate/Transaction/AdoNetWithDistributedTransactionFactory.cs @@ -115,7 +115,7 @@ void IEnlistmentNotification.Prepare(PreparingEnlistment preparingEnlistment) using (var tx = new TransactionScope(AmbientTransation)) { sessionImplementor.BeforeTransactionCompletion(null); - if (sessionImplementor.FlushMode != FlushMode.Never && sessionImplementor.ConnectionManager.IsConnected) + if (sessionImplementor.FlushMode != FlushMode.Manual && sessionImplementor.ConnectionManager.IsConnected) { using (sessionImplementor.ConnectionManager.FlushingFromDtcTransaction) { diff --git a/src/NHibernate/Transaction/AdoTransaction.cs b/src/NHibernate/Transaction/AdoTransaction.cs index 1cfd7a4fed9..162cba606e1 100644 --- a/src/NHibernate/Transaction/AdoTransaction.cs +++ b/src/NHibernate/Transaction/AdoTransaction.cs @@ -119,7 +119,7 @@ public void Begin(IsolationLevel isolationLevel) { throw new TransactionException("Cannot restart transaction after failed commit"); } - + if (isolationLevel == IsolationLevel.Unspecified) { isolationLevel = session.Factory.Settings.IsolationLevel; @@ -186,7 +186,7 @@ public void Commit() log.Debug("Start Commit"); - if (session.FlushMode != FlushMode.Never) + if (session.FlushMode != FlushMode.Manual) { session.Flush(); } @@ -426,10 +426,7 @@ private void NotifyLocalSynchsBeforeTransactionCompletion() catch (Exception e) { log.Error("exception calling user Synchronization", e); -#pragma warning disable 618 - if (!session.Factory.Settings.IsInterceptorsBeforeTransactionCompletionIgnoreExceptionsEnabled) - throw; -#pragma warning restore 618 + throw; } } } diff --git a/src/NHibernate/nhibernate-mapping.xsd b/src/NHibernate/nhibernate-mapping.xsd index ad16681ea85..b6a47597057 100644 --- a/src/NHibernate/nhibernate-mapping.xsd +++ b/src/NHibernate/nhibernate-mapping.xsd @@ -1682,8 +1682,13 @@ - + + + + Obsolete, please use manual instead. + +