Skip to content

NH-3606 - open a stateless session from a session #1520

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Mar 11, 2018
68 changes: 61 additions & 7 deletions src/NHibernate.Test/Async/SessionBuilder/Fixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,28 @@ private void CanSetAutoJoinTransaction<T>(T sb) where T : ISessionBuilder<T>
false, true);
}

private void CanSetAutoJoinTransactionOnStateless<T>(T sb) where T : IStatelessSessionBuilder
{
var options = DebugSessionFactory.GetCreationOptions(sb);
CanSetOnStateless(
sb, sb.AutoJoinTransaction, () => options.ShouldAutoJoinTransaction,
sb is ISharedStatelessSessionBuilder ssb ? ssb.AutoJoinTransaction : default(Func<ISharedStatelessSessionBuilder>),
// initial value
true,
// values
false, true);
}

[Test]
public async Task CanSetConnectionAsync()
{
var sb = Sfi.WithOptions();
await (CanSetConnectionAsync(sb));
await (CanSetConnectionOnStatelessAsync(Sfi.WithStatelessOptions()));
using (var s = sb.OpenSession())
{
await (CanSetConnectionAsync(s.SessionWithOptions()));
await (CanSetConnectionOnStatelessAsync(s.StatelessSessionWithOptions()));
}
}

Expand Down Expand Up @@ -109,12 +123,10 @@ public async Task CanSetConnectionAsync()
}
}

[Test]
public async Task CanSetConnectionOnStatelessAsync()
private async Task CanSetConnectionOnStatelessAsync<T>(T sb, CancellationToken cancellationToken = default(CancellationToken)) where T : IStatelessSessionBuilder
{
var sb = Sfi.WithStatelessOptions();
var sbType = sb.GetType().Name;
var conn = await (Sfi.ConnectionProvider.GetConnectionAsync(CancellationToken.None));
var conn = await (Sfi.ConnectionProvider.GetConnectionAsync(cancellationToken));
try
{
var options = DebugSessionFactory.GetCreationOptions(sb);
Expand All @@ -123,9 +135,31 @@ public async Task CanSetConnectionOnStatelessAsync()
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");
if (sb is ISharedStatelessSessionBuilder 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
{
Expand Down Expand Up @@ -218,5 +252,25 @@ private void CanSet<T, V>(T sb, Func<V, T> setter, Func<V> getter, Func<ISharedS
Assert.AreEqual(sb, fsb, $"{sbType}: Unexpected fluent return after call with {value}");
}
}

private void CanSetOnStateless<T, V>(
T sb, Func<V, T> setter, Func<V> getter, Func<ISharedStatelessSessionBuilder> shared, V initialValue,
params V[] values) where T : IStatelessSessionBuilder
{
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}");
}
}
}
}
31 changes: 30 additions & 1 deletion src/NHibernate.Test/Async/TransactionTest/TransactionFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,35 @@ public async Task FlushFromTransactionAppliesToSharingSessionAsync()
}
}

[Test]
public async Task FlushFromTransactionAppliesToSharingStatelessSessionAsync()
{
using (var s = OpenSession())
{
var builder = s.StatelessSessionWithOptions().Connection();

using (var s1 = builder.OpenStatelessSession())
using (var s2 = builder.OpenStatelessSession())
using (var t = s.BeginTransaction())
{
var p1 = new Person();
var p2 = new Person();
var p3 = new Person();
await (s1.InsertAsync(p1));
await (s2.InsertAsync(p2));
await (s.SaveAsync(p3));
await (t.CommitAsync());
}
}

using (var s = OpenSession())
using (var t = s.BeginTransaction())
{
Assert.That(await (s.Query<Person>().CountAsync()), Is.EqualTo(3));
await (t.CommitAsync());
}
}

// Taken and adjusted from NH1632 When_commiting_items_in_DTC_transaction_will_add_items_to_2nd_level_cache
[Test]
public async Task WhenCommittingItemsWillAddThemTo2ndLevelCacheAsync()
Expand Down Expand Up @@ -210,4 +239,4 @@ public async Task WhenCommittingItemsWillAddThemTo2ndLevelCacheAsync()
}
}
}
}
}
3 changes: 2 additions & 1 deletion src/NHibernate.Test/DebugSessionFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -386,7 +386,8 @@ public static ISessionCreationOptions GetCreationOptions<T>(ISessionBuilder<T> s

public static ISessionCreationOptions GetCreationOptions(IStatelessSessionBuilder sessionBuilder)
{
return ((StatelessSessionBuilder)sessionBuilder).CreationOptions;
return (sessionBuilder as StatelessSessionBuilder)?.CreationOptions ??
(ISessionCreationOptions)sessionBuilder;
}

internal class SessionBuilder : ISessionBuilder
Expand Down
77 changes: 58 additions & 19 deletions src/NHibernate.Test/SessionBuilder/Fixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,11 @@ public void CanSetAutoJoinTransaction()
{
var sb = Sfi.WithOptions();
CanSetAutoJoinTransaction(sb);
CanSetAutoJoinTransactionOnStateless(Sfi.WithStatelessOptions());
using (var s = sb.OpenSession())
{
CanSetAutoJoinTransaction(s.SessionWithOptions());
CanSetAutoJoinTransactionOnStateless(s.StatelessSessionWithOptions());
}
}

Expand All @@ -64,31 +66,28 @@ private void CanSetAutoJoinTransaction<T>(T sb) where T : ISessionBuilder<T>
false, true);
}

[Test]
public void CanSetAutoJoinTransactionOnStateless()
private void CanSetAutoJoinTransactionOnStateless<T>(T sb) where T : IStatelessSessionBuilder
{
var sb = Sfi.WithStatelessOptions();

var sbType = sb.GetType().Name;
var options = DebugSessionFactory.GetCreationOptions(sb);
Assert.That(options.ShouldAutoJoinTransaction, Is.True, $"{sbType}: Initial value");
var fsb = sb.AutoJoinTransaction(false);
Assert.That(options.ShouldAutoJoinTransaction, Is.False, $"{sbType}: After call with false");
Assert.That(fsb, Is.SameAs(sb), $"{sbType}: Unexpected fluent return after call with false");

fsb = sb.AutoJoinTransaction(true);
Assert.That(options.ShouldAutoJoinTransaction, Is.True, $"{sbType}: After call with true");
Assert.That(fsb, Is.SameAs(sb), $"{sbType}: Unexpected fluent return after call with true");
CanSetOnStateless(
sb, sb.AutoJoinTransaction, () => options.ShouldAutoJoinTransaction,
sb is ISharedStatelessSessionBuilder ssb ? ssb.AutoJoinTransaction : default(Func<ISharedStatelessSessionBuilder>),
// initial value
true,
// values
false, true);
}

[Test]
public void CanSetConnection()
{
var sb = Sfi.WithOptions();
CanSetConnection(sb);
CanSetConnectionOnStateless(Sfi.WithStatelessOptions());
using (var s = sb.OpenSession())
{
CanSetConnection(s.SessionWithOptions());
CanSetConnectionOnStateless(s.StatelessSessionWithOptions());
}
}

Expand Down Expand Up @@ -136,10 +135,8 @@ private void CanSetConnection<T>(T sb) where T : ISessionBuilder<T>
}
}

[Test]
public void CanSetConnectionOnStateless()
private void CanSetConnectionOnStateless<T>(T sb) where T : IStatelessSessionBuilder
{
var sb = Sfi.WithStatelessOptions();
var sbType = sb.GetType().Name;
var conn = Sfi.ConnectionProvider.GetConnection();
try
Expand All @@ -150,9 +147,31 @@ public void CanSetConnectionOnStateless()
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");
if (sb is ISharedStatelessSessionBuilder 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
{
Expand Down Expand Up @@ -259,5 +278,25 @@ private void CanSet<T, V>(T sb, Func<V, T> setter, Func<V> getter, Func<ISharedS
Assert.AreEqual(sb, fsb, $"{sbType}: Unexpected fluent return after call with {value}");
}
}

private void CanSetOnStateless<T, V>(
T sb, Func<V, T> setter, Func<V> getter, Func<ISharedStatelessSessionBuilder> shared, V initialValue,
params V[] values) where T : IStatelessSessionBuilder
{
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}");
}
}
}
}
31 changes: 30 additions & 1 deletion src/NHibernate.Test/TransactionTest/TransactionFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,35 @@ public void FlushFromTransactionAppliesToSharingSession()
}
}

[Test]
public void FlushFromTransactionAppliesToSharingStatelessSession()
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have not added the equivalent test in system transaction, because the "flush from transaction completion" feature is basically broken with stateless sessions. A stateless session accumulates changes as a pending batched command, bound to the current connection. When flushing from a system transaction, this means the command has to execute from the NHibernate enlisted resource prepare phase, potentially after (or concurrently to) its connection own prepare phase.

{
using (var s = OpenSession())
{
var builder = s.StatelessSessionWithOptions().Connection();

using (var s1 = builder.OpenStatelessSession())
using (var s2 = builder.OpenStatelessSession())
using (var t = s.BeginTransaction())
{
var p1 = new Person();
var p2 = new Person();
var p3 = new Person();
s1.Insert(p1);
s2.Insert(p2);
s.Save(p3);
t.Commit();
}
}

using (var s = OpenSession())
using (var t = s.BeginTransaction())
{
Assert.That(s.Query<Person>().Count(), Is.EqualTo(3));
t.Commit();
}
}

// Taken and adjusted from NH1632 When_commiting_items_in_DTC_transaction_will_add_items_to_2nd_level_cache
[Test]
public void WhenCommittingItemsWillAddThemTo2ndLevelCache()
Expand Down Expand Up @@ -213,4 +242,4 @@ public void WhenCommittingItemsWillAddThemTo2ndLevelCache()
}
}
}
}
}
2 changes: 2 additions & 0 deletions src/NHibernate/Async/ISession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,15 @@
using NHibernate.Engine;
using NHibernate.Event;
using NHibernate.Event.Default;
using NHibernate.Impl;
using NHibernate.Stat;
using NHibernate.Type;

namespace NHibernate
{
using System.Threading.Tasks;
using System.Threading;

public partial interface ISession : IDisposable
{

Expand Down
1 change: 1 addition & 0 deletions src/NHibernate/Async/Impl/AbstractSessionImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Linq;
using NHibernate.AdoNet;
Expand Down
1 change: 0 additions & 1 deletion src/NHibernate/Async/Impl/SessionImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Linq.Expressions;
using System.Runtime.Serialization;
Expand Down
3 changes: 0 additions & 3 deletions src/NHibernate/Async/Impl/StatelessSessionImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Linq.Expressions;
using NHibernate.AdoNet;
using NHibernate.Cache;
using NHibernate.Collection;
using NHibernate.Criterion;
Expand Down
10 changes: 9 additions & 1 deletion src/NHibernate/Engine/ISessionImplementor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -274,8 +274,16 @@ public partial interface ISessionImplementor
bool IsOpen { get; }

/// <summary>
/// Is the <c>ISession</c> currently connected?
/// Is the session connected?
/// </summary>
/// <value>
/// <see langword="true" /> if the session is connected.
/// </value>
/// <remarks>
/// A session is considered connected if there is a <see cref="DbConnection"/> (regardless
/// of its state) or if the field <c>connect</c> is true. Meaning that it will connect
/// at the next operation that requires a connection.
/// </remarks>
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This property concrete implementation being put in common, I have taken the more complete xml documentation.

bool IsConnected { get; }

FlushMode FlushMode { get; set; }
Expand Down
Loading