Skip to content

Commit 5780b2b

Browse files
committed
Tenant aware SchemaExport
1 parent c743b9c commit 5780b2b

File tree

5 files changed

+289
-53
lines changed

5 files changed

+289
-53
lines changed

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010

1111
using System;
12+
using System.Data.Common;
1213
using System.Data.SqlClient;
1314
using System.IO;
1415
using System.Linq;
@@ -187,6 +188,11 @@ protected override HbmMapping GetMappings()
187188
return mapper.CompileMappingForAllExplicitlyAddedEntities();
188189
}
189190

191+
protected override DbConnection OpenConnectionForSchemaExport()
192+
{
193+
return GetTenantConfig("defaultTenant").ConnectionAccess.GetConnection();
194+
}
195+
190196
protected override ISession OpenSession()
191197
{
192198
return OpenTenantSession("defaultTenant");

src/NHibernate.Test/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Data.Common;
23
using System.Data.SqlClient;
34
using System.IO;
45
using System.Linq;
@@ -207,6 +208,11 @@ protected override HbmMapping GetMappings()
207208
return mapper.CompileMappingForAllExplicitlyAddedEntities();
208209
}
209210

211+
protected override DbConnection OpenConnectionForSchemaExport()
212+
{
213+
return GetTenantConfig("defaultTenant").ConnectionAccess.GetConnection();
214+
}
215+
210216
protected override ISession OpenSession()
211217
{
212218
return OpenTenantSession("defaultTenant");

src/NHibernate.Test/TestCase.cs

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections;
33
using System.Collections.Generic;
44
using System.Data;
5+
using System.Data.Common;
56
using System.Reflection;
67
using log4net;
78
using NHibernate.Cfg;
@@ -282,15 +283,17 @@ protected virtual void AddMappings(Configuration configuration)
282283

283284
protected virtual void CreateSchema()
284285
{
285-
SchemaExport.Create(OutputDdl, true);
286+
using (var optionalConnection = OpenConnectionForSchemaExport())
287+
SchemaExport.Create(OutputDdl, true, optionalConnection);
286288
}
287289

288290
protected virtual void DropSchema()
289291
{
290-
DropSchema(OutputDdl, SchemaExport, Sfi);
292+
using (var optionalConnection = OpenConnectionForSchemaExport())
293+
DropSchema(OutputDdl, SchemaExport, Sfi, optionalConnection);
291294
}
292295

293-
public static void DropSchema(bool useStdOut, SchemaExport export, ISessionFactoryImplementor sfi)
296+
public static void DropSchema(bool useStdOut, SchemaExport export, ISessionFactoryImplementor sfi, DbConnection optionalConnection = null)
294297
{
295298
if (sfi?.ConnectionProvider.Driver is FirebirdClientDriver fbDriver)
296299
{
@@ -303,7 +306,15 @@ public static void DropSchema(bool useStdOut, SchemaExport export, ISessionFacto
303306
fbDriver.ClearPool(null);
304307
}
305308

306-
export.Drop(useStdOut, true);
309+
export.Drop(useStdOut, true, optionalConnection);
310+
}
311+
312+
/// <summary>
313+
/// Specific connection is required only for Database multi-tenancy. In other cases can be null
314+
/// </summary>
315+
protected virtual DbConnection OpenConnectionForSchemaExport()
316+
{
317+
return null;
307318
}
308319

309320
protected virtual DebugSessionFactory BuildSessionFactory()

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

Lines changed: 150 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
using NHibernate.AdoNet.Util;
1818
using NHibernate.Cfg;
1919
using NHibernate.Connection;
20+
using NHibernate.MultiTenancy;
2021
using NHibernate.Util;
2122
using Environment=NHibernate.Cfg.Environment;
2223

@@ -46,9 +47,11 @@ public partial class SchemaExport
4647
dropSQL = cfg.GenerateDropSchemaScript(dialect);
4748
createSQL = cfg.GenerateSchemaCreationScript(dialect);
4849
formatter = (PropertiesHelper.GetBoolean(Environment.FormatSql, configProperties, true) ? FormatStyle.Ddl : FormatStyle.None).Formatter;
50+
_requireTenantConnection = PropertiesHelper.GetEnum(Environment.MultiTenant, configProperties, MultiTenancyStrategy.None) == MultiTenancyStrategy.Database;
4951
wasInitialized = true;
5052
}
5153

54+
//TODO 6.0: Remove (replaced by method with optional connection parameter)
5255
/// <summary>
5356
/// Run the schema creation script
5457
/// </summary>
@@ -65,9 +68,38 @@ public partial class SchemaExport
6568
{
6669
return Task.FromCanceled<object>(cancellationToken);
6770
}
68-
return ExecuteAsync(useStdOut, execute, false, cancellationToken);
71+
return CreateAsync(useStdOut, execute, null, cancellationToken);
6972
}
7073

74+
/// <summary>
75+
/// Run the schema creation script
76+
/// </summary>
77+
/// <param name="useStdOut"><see langword="true" /> if the ddl should be outputted in the Console.</param>
78+
/// <param name="execute"><see langword="true" /> if the ddl should be executed against the Database.</param>
79+
/// <param name="connection"> Optional explicit connection. Required for multi-tenancy.
80+
/// Must be an opened connection. The method doesn't close the connection. </param>
81+
/// <param name="cancellationToken">A cancellation token that can be used to cancel the work</param>
82+
/// <remarks>
83+
/// This is a convenience method that calls <see cref="ExecuteAsync(bool, bool, bool,CancellationToken)"/> and sets
84+
/// the justDrop parameter to false.
85+
/// </remarks>
86+
public Task CreateAsync(bool useStdOut, bool execute, DbConnection connection = null, CancellationToken cancellationToken = default(CancellationToken))
87+
{
88+
if (cancellationToken.IsCancellationRequested)
89+
{
90+
return Task.FromCanceled<object>(cancellationToken);
91+
}
92+
try
93+
{
94+
return InitConnectionAndExecuteAsync(GetAction(useStdOut), execute, false, connection, null, cancellationToken);
95+
}
96+
catch (Exception ex)
97+
{
98+
return Task.FromException<object>(ex);
99+
}
100+
}
101+
102+
//TODO 6.0: Remove (replaced by method with optional connection parameter)
71103
/// <summary>
72104
/// Run the schema creation script
73105
/// </summary>
@@ -84,9 +116,31 @@ public partial class SchemaExport
84116
{
85117
return Task.FromCanceled<object>(cancellationToken);
86118
}
87-
return ExecuteAsync(scriptAction, execute, false, cancellationToken);
119+
return CreateAsync(scriptAction, execute, null, cancellationToken);
88120
}
89121

122+
/// <summary>
123+
/// Run the schema creation script
124+
/// </summary>
125+
/// <param name="scriptAction"> an action that will be called for each line of the generated ddl.</param>
126+
/// <param name="execute"><see langword="true" /> if the ddl should be executed against the Database.</param>
127+
/// <param name="connection"> Optional explicit connection. Required for multi-tenancy.
128+
/// Must be an opened connection. The method doesn't close the connection. </param>
129+
/// <param name="cancellationToken">A cancellation token that can be used to cancel the work</param>
130+
/// <remarks>
131+
/// This is a convenience method that calls <see cref="ExecuteAsync(bool, bool, bool,CancellationToken)"/> and sets
132+
/// the justDrop parameter to false.
133+
/// </remarks>
134+
public Task CreateAsync(Action<string> scriptAction, bool execute, DbConnection connection = null, CancellationToken cancellationToken = default(CancellationToken))
135+
{
136+
if (cancellationToken.IsCancellationRequested)
137+
{
138+
return Task.FromCanceled<object>(cancellationToken);
139+
}
140+
return InitConnectionAndExecuteAsync(scriptAction, execute, false, connection, null, cancellationToken);
141+
}
142+
143+
//TODO 6.0: Remove (replaced by method with optional connection parameter)
90144
/// <summary>
91145
/// Run the schema creation script
92146
/// </summary>
@@ -103,9 +157,31 @@ public partial class SchemaExport
103157
{
104158
return Task.FromCanceled<object>(cancellationToken);
105159
}
106-
return ExecuteAsync(null, execute, false, exportOutput, cancellationToken);
160+
return CreateAsync(exportOutput, execute, null, cancellationToken);
107161
}
108162

163+
/// <summary>
164+
/// Run the schema creation script
165+
/// </summary>
166+
/// <param name="exportOutput"> if non-null, the ddl will be written to this TextWriter.</param>
167+
/// <param name="execute"><see langword="true" /> if the ddl should be executed against the Database.</param>
168+
/// <param name="connection"> Optional explicit connection. Required for multi-tenancy.
169+
/// Must be an opened connection. The method doesn't close the connection. </param>
170+
/// <param name="cancellationToken">A cancellation token that can be used to cancel the work</param>
171+
/// <remarks>
172+
/// This is a convenience method that calls <see cref="ExecuteAsync(bool, bool, bool,CancellationToken)"/> and sets
173+
/// the justDrop parameter to false.
174+
/// </remarks>
175+
public Task CreateAsync(TextWriter exportOutput, bool execute, DbConnection connection = null, CancellationToken cancellationToken = default(CancellationToken))
176+
{
177+
if (cancellationToken.IsCancellationRequested)
178+
{
179+
return Task.FromCanceled<object>(cancellationToken);
180+
}
181+
return InitConnectionAndExecuteAsync(null, execute, false, connection, exportOutput, cancellationToken);
182+
}
183+
184+
//TODO 6.0: Remove (replaced by method with optional connection parameter)
109185
/// <summary>
110186
/// Run the drop schema script
111187
/// </summary>
@@ -122,9 +198,38 @@ public partial class SchemaExport
122198
{
123199
return Task.FromCanceled<object>(cancellationToken);
124200
}
125-
return ExecuteAsync(useStdOut, execute, true, cancellationToken);
201+
return DropAsync(useStdOut, execute, null, cancellationToken);
202+
}
203+
204+
/// <summary>
205+
/// Run the drop schema script
206+
/// </summary>
207+
/// <param name="useStdOut"><see langword="true" /> if the ddl should be outputted in the Console.</param>
208+
/// <param name="execute"><see langword="true" /> if the ddl should be executed against the Database.</param>
209+
/// <param name="connection"> Optional explicit connection. Required for multi-tenancy.
210+
/// Must be an opened connection. The method doesn't close the connection. </param>
211+
/// <param name="cancellationToken">A cancellation token that can be used to cancel the work</param>
212+
/// <remarks>
213+
/// This is a convenience method that calls <see cref="ExecuteAsync(bool, bool, bool,CancellationToken)"/> and sets
214+
/// the justDrop parameter to true.
215+
/// </remarks>
216+
public Task DropAsync(bool useStdOut, bool execute, DbConnection connection = null, CancellationToken cancellationToken = default(CancellationToken))
217+
{
218+
if (cancellationToken.IsCancellationRequested)
219+
{
220+
return Task.FromCanceled<object>(cancellationToken);
221+
}
222+
try
223+
{
224+
return InitConnectionAndExecuteAsync(GetAction(useStdOut), execute, true, connection, null, cancellationToken);
225+
}
226+
catch (Exception ex)
227+
{
228+
return Task.FromException<object>(ex);
229+
}
126230
}
127231

232+
//TODO 6.0: Remove (replaced by method with optional connection parameter)
128233
/// <summary>
129234
/// Run the drop schema script
130235
/// </summary>
@@ -141,7 +246,28 @@ public partial class SchemaExport
141246
{
142247
return Task.FromCanceled<object>(cancellationToken);
143248
}
144-
return ExecuteAsync(null, execute, true, exportOutput, cancellationToken);
249+
return DropAsync(exportOutput, execute, null, cancellationToken);
250+
}
251+
252+
/// <summary>
253+
/// Run the drop schema script
254+
/// </summary>
255+
/// <param name="exportOutput"> if non-null, the ddl will be written to this TextWriter.</param>
256+
/// <param name="execute"><see langword="true" /> if the ddl should be executed against the Database.</param>
257+
/// <param name="connection"> Optional explicit connection. Required for multi-tenancy.
258+
/// Must be an opened connection. The method doesn't close the connection. </param>
259+
/// <param name="cancellationToken">A cancellation token that can be used to cancel the work</param>
260+
/// <remarks>
261+
/// This is a convenience method that calls <see cref="ExecuteAsync(Action&lt;string&gt;, bool, bool, TextWriter,CancellationToken)"/> and sets
262+
/// the justDrop parameter to true.
263+
/// </remarks>
264+
public Task DropAsync(TextWriter exportOutput, bool execute, DbConnection connection = null, CancellationToken cancellationToken = default(CancellationToken))
265+
{
266+
if (cancellationToken.IsCancellationRequested)
267+
{
268+
return Task.FromCanceled<object>(cancellationToken);
269+
}
270+
return InitConnectionAndExecuteAsync(null, execute, true, connection, exportOutput, cancellationToken);
145271
}
146272

147273
private async Task ExecuteInitializedAsync(Action<string> scriptAction, bool execute, bool throwOnError, TextWriter exportOutput,
@@ -228,14 +354,7 @@ public Task ExecuteAsync(bool useStdOut, bool execute, bool justDrop, DbConnecti
228354
}
229355
try
230356
{
231-
if (useStdOut)
232-
{
233-
return ExecuteAsync(Console.WriteLine, execute, justDrop, connection, exportOutput, cancellationToken);
234-
}
235-
else
236-
{
237-
return ExecuteAsync(null, execute, justDrop, connection, exportOutput, cancellationToken);
238-
}
357+
return ExecuteAsync(GetAction(useStdOut), execute, justDrop, connection, exportOutput, cancellationToken);
239358
}
240359
catch (Exception ex)
241360
{
@@ -319,14 +438,7 @@ public async Task ExecuteAsync(Action<string> scriptAction, bool execute, bool j
319438
}
320439
try
321440
{
322-
if (useStdOut)
323-
{
324-
return ExecuteAsync(Console.WriteLine, execute, justDrop, cancellationToken);
325-
}
326-
else
327-
{
328-
return ExecuteAsync(null, execute, justDrop, cancellationToken);
329-
}
441+
return InitConnectionAndExecuteAsync(GetAction(useStdOut), execute, justDrop, null, null, cancellationToken);
330442
}
331443
catch (Exception ex)
332444
{
@@ -343,11 +455,19 @@ public async Task ExecuteAsync(Action<string> scriptAction, bool execute, bool j
343455
return ExecuteAsync(scriptAction, execute, justDrop, null, cancellationToken);
344456
}
345457

346-
public async Task ExecuteAsync(Action<string> scriptAction, bool execute, bool justDrop, TextWriter exportOutput, CancellationToken cancellationToken = default(CancellationToken))
458+
public Task ExecuteAsync(Action<string> scriptAction, bool execute, bool justDrop, TextWriter exportOutput, CancellationToken cancellationToken = default(CancellationToken))
459+
{
460+
if (cancellationToken.IsCancellationRequested)
461+
{
462+
return Task.FromCanceled<object>(cancellationToken);
463+
}
464+
return InitConnectionAndExecuteAsync(scriptAction, execute, justDrop, null, exportOutput, cancellationToken);
465+
}
466+
467+
private async Task InitConnectionAndExecuteAsync(Action<string> scriptAction, bool execute, bool justDrop, DbConnection connection, TextWriter exportOutput, CancellationToken cancellationToken = default(CancellationToken))
347468
{
348469
cancellationToken.ThrowIfCancellationRequested();
349470
await (InitializeAsync(cancellationToken)).ConfigureAwait(false);
350-
DbConnection connection = null;
351471
TextWriter fileOutput = exportOutput;
352472
IConnectionProvider connectionProvider = null;
353473

@@ -358,8 +478,13 @@ public async Task ExecuteAsync(Action<string> scriptAction, bool execute, bool j
358478
fileOutput = new StreamWriter(outputFile);
359479
}
360480

361-
if (execute)
481+
if (execute && connection == null)
362482
{
483+
if (_requireTenantConnection)
484+
{
485+
throw new ArgumentException("When Database multi-tenancy is enabled you need to provide explicit connection. Please use overload with connection parameter.");
486+
}
487+
363488
var props = new Dictionary<string, string>();
364489
foreach (var de in dialect.DefaultProperties)
365490
{
@@ -393,7 +518,7 @@ public async Task ExecuteAsync(Action<string> scriptAction, bool execute, bool j
393518
}
394519
finally
395520
{
396-
if (connection != null)
521+
if (connectionProvider != null)
397522
{
398523
connectionProvider.CloseConnection(connection);
399524
connectionProvider.Dispose();

0 commit comments

Comments
 (0)