Skip to content

Commit 680347e

Browse files
committed
Shared and stateless session support;
Rename MultiTenant to MultiTenancy for consistency; Added proper session tenant info serialization
1 parent fcc79c2 commit 680347e

19 files changed

+223
-38
lines changed

src/NHibernate.Test/Async/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ public class DatabaseStrategyNoDbSpecificFixtureAsync : TestCaseMappingByCode
3636

3737
protected override void Configure(Configuration configuration)
3838
{
39-
configuration.Properties[Cfg.Environment.MultiTenant] = MultiTenancyStrategy.Database.ToString();
40-
configuration.Properties[Cfg.Environment.GenerateStatistics] = true.ToString();
39+
configuration.Properties[Cfg.Environment.MultiTenancy] = MultiTenancyStrategy.Database.ToString();
40+
configuration.Properties[Cfg.Environment.GenerateStatistics] = "true";
4141
base.Configure(configuration);
4242
}
4343

@@ -47,6 +47,12 @@ private static void ValidateSqlServerConnectionAppName(ISession s, string tenant
4747
Assert.That(builder.ApplicationName, Is.EqualTo(tenantId));
4848
}
4949

50+
private static void ValidateSqlServerConnectionAppName(IStatelessSession s, string tenantId)
51+
{
52+
var builder = new SqlConnectionStringBuilder(s.Connection.ConnectionString);
53+
Assert.That(builder.ApplicationName, Is.EqualTo(tenantId));
54+
}
55+
5056
[Test]
5157
public async Task SecondLevelCacheReusedForSameTenantAsync()
5258
{
@@ -118,7 +124,7 @@ public async Task QueryCacheSeparationPerTenantAsync()
118124
Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(1));
119125
Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(0));
120126
}
121-
127+
122128
[Test]
123129
public async Task TenantSessionIsSerializableAndCanBeReconnectedAsync()
124130
{
@@ -134,35 +140,57 @@ public async Task TenantSessionIsSerializableAndCanBeReconnectedAsync()
134140
using (deserializedSession)
135141
{
136142
deserializedSession.Reconnect();
143+
144+
//Expect session cache hit
137145
var entity = await (deserializedSession.GetAsync<Entity>(_id));
138146
if (IsSqlServerDialect)
139147
ValidateSqlServerConnectionAppName(deserializedSession, "tenant1");
148+
deserializedSession.Clear();
149+
150+
//Expect second level cache hit
151+
await (deserializedSession.GetAsync<Entity>(_id));
152+
Assert.That(GetTenantId(deserializedSession), Is.EqualTo("tenant1"));
140153
}
141154

142155
Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0));
143-
Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(0));
156+
Assert.That(Sfi.Statistics.SecondLevelCacheHitCount, Is.EqualTo(1));
157+
}
158+
159+
private static string GetTenantId(ISession deserializedSession)
160+
{
161+
return deserializedSession.GetSessionImplementation().GetTenantIdentifier();
162+
}
163+
164+
private static string GetTenantId(IStatelessSession deserializedSession)
165+
{
166+
return deserializedSession.GetSessionImplementation().GetTenantIdentifier();
144167
}
145168

146-
private ISession SpoofSerialization(ISession session)
169+
private T SpoofSerialization<T>(T session)
147170
{
148171
var formatter = new BinaryFormatter
149172
{
150173
#if !NETFX
151-
SurrogateSelector = new SerializationHelper.SurrogateSelector()
174+
SurrogateSelector = new SerializationHelper.SurrogateSelector()
152175
#endif
153176
};
154177
MemoryStream stream = new MemoryStream();
155178
formatter.Serialize(stream, session);
156179

157180
stream.Position = 0;
158181

159-
return (ISession) formatter.Deserialize(stream);
182+
return (T) formatter.Deserialize(stream);
160183
}
161184

162185
private ISession OpenTenantSession(string tenantId)
163186
{
164187
return Sfi.WithOptions().TenantConfiguration(GetTenantConfig(tenantId)).OpenSession();
165188
}
189+
190+
private IStatelessSession OpenTenantStatelessSession(string tenantId)
191+
{
192+
return Sfi.WithStatelessOptions().TenantConfiguration(GetTenantConfig(tenantId)).OpenStatelessSession();
193+
}
166194

167195
private TenantConfiguration GetTenantConfig(string tenantId)
168196
{

src/NHibernate.Test/DebugSessionFactory.cs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -401,7 +401,9 @@ public static ISessionCreationOptions GetCreationOptions(IStatelessSessionBuilde
401401
(ISessionCreationOptions)sessionBuilder;
402402
}
403403

404-
internal class SessionBuilder : ISessionBuilder, ISessionCreationOptionsWithMultiTenancy
404+
internal class SessionBuilder : ISessionBuilder,
405+
//TODO 6.0: Remove interface with implementation (will be replaced TenantConfiguration ISessionBuilder method)
406+
ISessionCreationOptionsWithMultiTenancy
405407
{
406408
private readonly ISessionBuilder _actualBuilder;
407409
private readonly DebugSessionFactory _debugFactory;
@@ -474,7 +476,9 @@ TenantConfiguration ISessionCreationOptionsWithMultiTenancy.TenantConfiguration
474476
}
475477
}
476478

477-
internal class StatelessSessionBuilder : IStatelessSessionBuilder
479+
internal class StatelessSessionBuilder : IStatelessSessionBuilder,
480+
//TODO 6.0: Remove interface with implementation (will be replaced TenantConfiguration IStatelessSessionBuilder method)
481+
ISessionCreationOptionsWithMultiTenancy
478482
{
479483
private readonly IStatelessSessionBuilder _actualBuilder;
480484
private readonly DebugSessionFactory _debugFactory;
@@ -508,6 +512,12 @@ IStatelessSessionBuilder IStatelessSessionBuilder.AutoJoinTransaction(bool autoJ
508512
return this;
509513
}
510514

515+
TenantConfiguration ISessionCreationOptionsWithMultiTenancy.TenantConfiguration
516+
{
517+
get => (_actualBuilder as ISessionCreationOptionsWithMultiTenancy)?.TenantConfiguration;
518+
set => _actualBuilder.TenantConfiguration(value);
519+
}
520+
511521
#endregion
512522
}
513523
}

src/NHibernate.Test/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs

Lines changed: 106 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ public class DatabaseStrategyNoDbSpecificFixture : TestCaseMappingByCode
2525

2626
protected override void Configure(Configuration configuration)
2727
{
28-
configuration.Properties[Cfg.Environment.MultiTenant] = MultiTenancyStrategy.Database.ToString();
29-
configuration.Properties[Cfg.Environment.GenerateStatistics] = true.ToString();
28+
configuration.Properties[Cfg.Environment.MultiTenancy] = MultiTenancyStrategy.Database.ToString();
29+
configuration.Properties[Cfg.Environment.GenerateStatistics] = "true";
3030
base.Configure(configuration);
3131
}
3232

@@ -37,11 +37,11 @@ public void ShouldThrowWithNoTenantIdentifier()
3737

3838
Assert.That(() => sessionBuilder.OpenSession(), Throws.ArgumentException);
3939
}
40-
40+
4141
[Test]
4242
public void ShouldThrowWithNoConnectionAccess()
4343
{
44-
var sessionBuilder = Sfi.WithOptions().TenantConfiguration(new TenantConfiguration(new MockConnectionProvider("tenant1", null)));
44+
var sessionBuilder = Sfi.WithOptions().TenantConfiguration(new TenantConfiguration(new MockConnectionProvider("tenant1", null)));
4545

4646
Assert.That(() => sessionBuilder.OpenSession(), Throws.ArgumentException);
4747
}
@@ -58,6 +58,75 @@ public void DifferentConnectionStringForDifferentTenants()
5858
Assert.That(session1.Connection.ConnectionString, Is.Not.EqualTo(session2.Connection.ConnectionString));
5959
ValidateSqlServerConnectionAppName(session1, "tenant1");
6060
ValidateSqlServerConnectionAppName(session2, "tenant2");
61+
Assert.That(GetTenantId(session1), Is.EqualTo("tenant1"));
62+
Assert.That(GetTenantId(session2), Is.EqualTo("tenant2"));
63+
}
64+
}
65+
66+
[Test]
67+
public void StatelessSessionShouldThrowWithNoTenantIdentifier()
68+
{
69+
var sessionBuilder = Sfi.WithStatelessOptions().TenantConfiguration(new TenantConfiguration(new MockConnectionProvider(null, null)));
70+
71+
Assert.That(() => sessionBuilder.OpenStatelessSession(), Throws.ArgumentException);
72+
}
73+
74+
[Test]
75+
public void StatelessSessionShouldThrowWithNoConnectionAccess()
76+
{
77+
var sessionBuilder = Sfi.WithStatelessOptions().TenantConfiguration(new TenantConfiguration(new MockConnectionProvider("tenant1", null)));
78+
79+
Assert.That(() => sessionBuilder.OpenStatelessSession(), Throws.ArgumentException);
80+
}
81+
82+
[Test]
83+
public void StatelessSessionDifferentConnectionStringForDifferentTenants()
84+
{
85+
if (!IsSqlServerDialect)
86+
Assert.Ignore("MSSqlServer specific test");
87+
88+
using(var session1 = OpenTenantStatelessSession("tenant1"))
89+
using (var session2 = OpenTenantStatelessSession("tenant2"))
90+
{
91+
Assert.That(session1.Connection.ConnectionString, Is.Not.EqualTo(session2.Connection.ConnectionString));
92+
ValidateSqlServerConnectionAppName(session1, "tenant1");
93+
ValidateSqlServerConnectionAppName(session2, "tenant2");
94+
Assert.That(GetTenantId(session1), Is.EqualTo("tenant1"));
95+
Assert.That(GetTenantId(session2), Is.EqualTo("tenant2"));
96+
}
97+
}
98+
99+
[Test]
100+
public void SharedSessionSameConnectionString()
101+
{
102+
using (var session1 = OpenTenantSession("tenant1"))
103+
using (var session2 = session1.SessionWithOptions().OpenSession())
104+
{
105+
Assert.That(session1.Connection, Is.Not.EqualTo(session2.Connection));
106+
Assert.That(session1.Connection.ConnectionString, Is.EqualTo(session2.Connection.ConnectionString));
107+
Assert.That(session2.GetSessionImplementation().GetTenantIdentifier(), Is.EqualTo("tenant1"));
108+
}
109+
}
110+
111+
[Test]
112+
public void SharedSessionSameConnection()
113+
{
114+
using (var session1 = OpenTenantSession("tenant1"))
115+
using (var session2 = session1.SessionWithOptions().Connection().OpenSession())
116+
{
117+
Assert.That(session1.Connection, Is.EqualTo(session2.Connection));
118+
Assert.That(GetTenantId(session2), Is.EqualTo("tenant1"));
119+
}
120+
}
121+
122+
[Test]
123+
public void SharedStatelessSessionSameConnectionString()
124+
{
125+
using (var session1 = OpenTenantSession("tenant1"))
126+
using (var session2 = session1.StatelessSessionWithOptions().OpenStatelessSession())
127+
{
128+
Assert.That(session1.Connection.ConnectionString, Is.EqualTo(session2.Connection.ConnectionString));
129+
Assert.That(GetTenantId(session2), Is.EqualTo("tenant1"));
61130
}
62131
}
63132

@@ -67,6 +136,12 @@ private static void ValidateSqlServerConnectionAppName(ISession s, string tenant
67136
Assert.That(builder.ApplicationName, Is.EqualTo(tenantId));
68137
}
69138

139+
private static void ValidateSqlServerConnectionAppName(IStatelessSession s, string tenantId)
140+
{
141+
var builder = new SqlConnectionStringBuilder(s.Connection.ConnectionString);
142+
Assert.That(builder.ApplicationName, Is.EqualTo(tenantId));
143+
}
144+
70145
[Test]
71146
public void SecondLevelCacheReusedForSameTenant()
72147
{
@@ -138,7 +213,7 @@ public void QueryCacheSeparationPerTenant()
138213
Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(1));
139214
Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(0));
140215
}
141-
216+
142217
[Test]
143218
public void TenantSessionIsSerializableAndCanBeReconnected()
144219
{
@@ -154,35 +229,57 @@ public void TenantSessionIsSerializableAndCanBeReconnected()
154229
using (deserializedSession)
155230
{
156231
deserializedSession.Reconnect();
232+
233+
//Expect session cache hit
157234
var entity = deserializedSession.Get<Entity>(_id);
158235
if (IsSqlServerDialect)
159236
ValidateSqlServerConnectionAppName(deserializedSession, "tenant1");
237+
deserializedSession.Clear();
238+
239+
//Expect second level cache hit
240+
deserializedSession.Get<Entity>(_id);
241+
Assert.That(GetTenantId(deserializedSession), Is.EqualTo("tenant1"));
160242
}
161243

162244
Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0));
163-
Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(0));
245+
Assert.That(Sfi.Statistics.SecondLevelCacheHitCount, Is.EqualTo(1));
164246
}
165247

166-
private ISession SpoofSerialization(ISession session)
248+
private static string GetTenantId(ISession deserializedSession)
249+
{
250+
return deserializedSession.GetSessionImplementation().GetTenantIdentifier();
251+
}
252+
253+
private static string GetTenantId(IStatelessSession deserializedSession)
254+
{
255+
return deserializedSession.GetSessionImplementation().GetTenantIdentifier();
256+
}
257+
258+
private T SpoofSerialization<T>(T session)
167259
{
168260
var formatter = new BinaryFormatter
169261
{
170262
#if !NETFX
171-
SurrogateSelector = new SerializationHelper.SurrogateSelector()
263+
SurrogateSelector = new SerializationHelper.SurrogateSelector()
172264
#endif
173265
};
174266
MemoryStream stream = new MemoryStream();
175267
formatter.Serialize(stream, session);
176268

177269
stream.Position = 0;
178270

179-
return (ISession) formatter.Deserialize(stream);
271+
return (T) formatter.Deserialize(stream);
180272
}
181273

182274
private ISession OpenTenantSession(string tenantId)
183275
{
184276
return Sfi.WithOptions().TenantConfiguration(GetTenantConfig(tenantId)).OpenSession();
185277
}
278+
279+
private IStatelessSession OpenTenantStatelessSession(string tenantId)
280+
{
281+
return Sfi.WithStatelessOptions().TenantConfiguration(GetTenantConfig(tenantId)).OpenStatelessSession();
282+
}
186283

187284
private TenantConfiguration GetTenantConfig(string tenantId)
188285
{

src/NHibernate/Async/Engine/ISessionImplementor.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ namespace NHibernate.Engine
3030
{
3131
using System.Threading.Tasks;
3232
using System.Threading;
33-
internal static partial class SessionImplementorExtensions
33+
public static partial class SessionImplementorExtensions
3434
{
3535

3636
internal static async Task AutoFlushIfRequiredAsync(this ISessionImplementor implementor, ISet<string> querySpaces, CancellationToken cancellationToken)

src/NHibernate/Async/Impl/SessionImpl.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
using NHibernate.Intercept;
2727
using NHibernate.Loader.Criteria;
2828
using NHibernate.Loader.Custom;
29+
using NHibernate.MultiTenancy;
2930
using NHibernate.Persister.Collection;
3031
using NHibernate.Persister.Entity;
3132
using NHibernate.Proxy;

src/NHibernate/Async/Tool/hbm2ddl/SchemaExport.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public partial class SchemaExport
4747
dropSQL = cfg.GenerateDropSchemaScript(dialect);
4848
createSQL = cfg.GenerateSchemaCreationScript(dialect);
4949
formatter = (PropertiesHelper.GetBoolean(Environment.FormatSql, configProperties, true) ? FormatStyle.Ddl : FormatStyle.None).Formatter;
50-
_requireTenantConnection = PropertiesHelper.GetEnum(Environment.MultiTenant, configProperties, MultiTenancyStrategy.None) == MultiTenancyStrategy.Database;
50+
_requireTenantConnection = PropertiesHelper.GetEnum(Environment.MultiTenancy, configProperties, MultiTenancyStrategy.None) == MultiTenancyStrategy.Database;
5151
wasInitialized = true;
5252
}
5353

src/NHibernate/Cfg/Environment.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,7 @@ public static string Version
298298
/// <summary>
299299
/// Strategy for multi-tenancy. </summary>
300300
/// <seealso cref="MultiTenancyStrategy"/>
301-
public const string MultiTenant = "multiTenancy";
301+
public const string MultiTenancy = "multiTenancy";
302302

303303
private static IBytecodeProvider BytecodeProviderInstance;
304304
private static bool EnableReflectionOptimizer;

src/NHibernate/Cfg/Loquacious/DbIntegrationConfigurationProperties.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,11 @@ public void QueryModelRewriterFactory<TFactory>() where TFactory : IQueryModelRe
129129
configuration.SetProperty(Environment.QueryModelRewriterFactory, typeof(TFactory).AssemblyQualifiedName);
130130
}
131131

132+
public MultiTenancy.MultiTenancyStrategy MultiTenancy
133+
{
134+
set { configuration.SetProperty(Environment.MultiTenancy, value.ToString()); }
135+
}
136+
132137
#endregion
133138
}
134-
}
139+
}

src/NHibernate/Cfg/Loquacious/IDbIntegrationConfigurationProperties.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,5 +41,8 @@ public interface IDbIntegrationConfigurationProperties
4141
SchemaAutoAction SchemaAction { set; }
4242

4343
void QueryModelRewriterFactory<TFactory>() where TFactory : IQueryModelRewriterFactory;
44+
45+
//TODO 6.0: Uncomment. Or even better consider dropping all configuration interfaces and use classes directly
46+
//MultiTenancy.MultiTenancyStrategy MultiTenancy { set; }
4447
}
4548
}

src/NHibernate/Cfg/SettingsFactory.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ public Settings BuildSettings(IDictionary<string, string> properties)
308308
log.Debug("Track session id: " + EnabledDisabled(trackSessionId));
309309
settings.TrackSessionId = trackSessionId;
310310

311-
var multiTenancyStrategy = PropertiesHelper.GetEnum(Environment.MultiTenant, properties, MultiTenancyStrategy.None);
311+
var multiTenancyStrategy = PropertiesHelper.GetEnum(Environment.MultiTenancy, properties, MultiTenancyStrategy.None);
312312
if(multiTenancyStrategy != MultiTenancyStrategy.None)
313313
log.Debug("multi-tenancy strategy : " + multiTenancyStrategy);
314314
settings.MultiTenancyStrategy = multiTenancyStrategy;

src/NHibernate/Engine/ISessionImplementor.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,14 @@
1919
namespace NHibernate.Engine
2020
{
2121
// 6.0 TODO: Convert to interface methods, excepted SwitchCacheMode
22-
internal static partial class SessionImplementorExtensions
22+
public static partial class SessionImplementorExtensions
2323
{
2424
//6.0 TODO: Expose as TenantIdentifier property
2525
/// <summary>
2626
/// Obtain the tenant identifier associated with this session.
2727
/// </summary>
2828
/// <returns> The tenant identifier associated with this session or null </returns>
29-
internal static string GetTenantIdentifier(this ISessionImplementor session)
29+
public static string GetTenantIdentifier(this ISessionImplementor session)
3030
{
3131
if (session is AbstractSessionImpl sessionImpl)
3232
{

0 commit comments

Comments
 (0)