diff --git a/src/Marten.Testing/Harness/TenantDatabases.cs b/src/Marten.Testing/Harness/TenantDatabases.cs new file mode 100644 index 0000000000..7fb8330158 --- /dev/null +++ b/src/Marten.Testing/Harness/TenantDatabases.cs @@ -0,0 +1,53 @@ +#nullable enable +using System.Threading.Tasks; +using Npgsql; +using Weasel.Postgresql; +using Weasel.Postgresql.Migrations; + +namespace Marten.Testing.Harness +{ + /// + /// Provisions per-tenant databases on the test PostgreSQL server for + /// database-per-tenant multi-tenancy tests. Databases are created once and + /// reused across runs (creation is expensive); callers are responsible for + /// resetting schemas or data inside them. + /// + public static class TenantDatabases + { + /// + /// Ensure the named database exists on the server behind + /// (an open connection to the master test + /// database) and return a connection string targeting it. + /// + public static async Task CreateIfNotExistsAsync(NpgsqlConnection conn, string databaseName) + { + if (!await conn.DatabaseExists(databaseName)) + { + try + { + await new DatabaseSpecification().BuildDatabase(conn, databaseName); + } + catch (PostgresException e) when (e.SqlState is PostgresErrorCodes.DuplicateDatabase + or PostgresErrorCodes.UniqueViolation) + { + // The test assemblies can run once per target framework, concurrently; + // the check-then-create above is not atomic, so the loser of that race + // sees the database it wanted already there. + } + } + + return ConnectionStringFor(databaseName); + } + + /// + /// A connection string for the named database on the test server. + /// + public static string ConnectionStringFor(string databaseName) + { + return new NpgsqlConnectionStringBuilder(ConnectionSource.ConnectionString) + { + Database = databaseName + }.ConnectionString; + } + } +} diff --git a/src/MultiTenancyTests/DocumentStore_IMartenStorage_implementation.cs b/src/MultiTenancyTests/DocumentStore_IMartenStorage_implementation.cs index b810e3cf05..cc92e80659 100644 --- a/src/MultiTenancyTests/DocumentStore_IMartenStorage_implementation.cs +++ b/src/MultiTenancyTests/DocumentStore_IMartenStorage_implementation.cs @@ -16,25 +16,15 @@ namespace MultiTenancyTests; -[CollectionDefinition("multi-tenancy", DisableParallelization = true)] +[Collection("multi-tenancy")] public class DocumentStore_IMartenStorage_implementation : IAsyncLifetime { private IHost _host; private IDocumentStore theStore; - private async Task CreateDatabaseIfNotExists(NpgsqlConnection conn, string databaseName) + private static async Task CreateDatabaseIfNotExists(NpgsqlConnection conn, string databaseName) { - var builder = new NpgsqlConnectionStringBuilder(ConnectionSource.ConnectionString); - - var exists = await conn.DatabaseExists(databaseName); - if (!exists) - { - await new DatabaseSpecification().BuildDatabase(conn, databaseName); - } - - builder.Database = databaseName; - - var connectionString = builder.ConnectionString; + var connectionString = await TenantDatabases.CreateIfNotExistsAsync(conn, databaseName); await SchemaUtils.DropSchema(connectionString, "multi_tenancy"); await SchemaUtils.DropSchema(connectionString, "mt_events"); diff --git a/src/MultiTenancyTests/MultiTenancyCollection.cs b/src/MultiTenancyTests/MultiTenancyCollection.cs new file mode 100644 index 0000000000..33090c16ef --- /dev/null +++ b/src/MultiTenancyTests/MultiTenancyCollection.cs @@ -0,0 +1,9 @@ +using Xunit; + +namespace MultiTenancyTests; + +// The single definition of the "multi-tenancy" collection. The classes in this +// collection all provision real per-tenant databases against the shared test +// server, so they must never run concurrently with each other. +[CollectionDefinition("multi-tenancy", DisableParallelization = true)] +public class MultiTenancyCollection; diff --git a/src/MultiTenancyTests/MultiTenancyTests.csproj b/src/MultiTenancyTests/MultiTenancyTests.csproj index 8254aa7fdd..e1306f84cf 100644 --- a/src/MultiTenancyTests/MultiTenancyTests.csproj +++ b/src/MultiTenancyTests/MultiTenancyTests.csproj @@ -116,6 +116,9 @@ Harness\StoreContext.cs + + Harness\TenantDatabases.cs + Harness\StoreFixture.cs diff --git a/src/MultiTenancyTests/SingleServerMultiTenancyTests.cs b/src/MultiTenancyTests/SingleServerMultiTenancyTests.cs index e27532e158..c6591a2384 100644 --- a/src/MultiTenancyTests/SingleServerMultiTenancyTests.cs +++ b/src/MultiTenancyTests/SingleServerMultiTenancyTests.cs @@ -13,7 +13,7 @@ namespace MultiTenancyTests; -[CollectionDefinition("multi-tenancy", DisableParallelization = true)] +[Collection("multi-tenancy")] public class SingleServerMultiTenancyTests: IAsyncLifetime { private DefaultNpgsqlDataSourceFactory dataSourceFactory = new(); diff --git a/src/MultiTenancyTests/projection_statuses_per_database.cs b/src/MultiTenancyTests/projection_statuses_per_database.cs index 6023efbcb8..315bd925b9 100644 --- a/src/MultiTenancyTests/projection_statuses_per_database.cs +++ b/src/MultiTenancyTests/projection_statuses_per_database.cs @@ -67,8 +67,8 @@ public async ValueTask InitializeAsync() await using var conn = new NpgsqlConnection(ConnectionSource.ConnectionString); await conn.OpenAsync(); await conn.DropSchemaAsync(MasterSchema); - var tenantA = await CreateDatabaseIfNotExists(conn, TenantA); - var tenantB = await CreateDatabaseIfNotExists(conn, TenantB); + var tenantA = await TenantDatabases.CreateIfNotExistsAsync(conn, TenantA); + var tenantB = await TenantDatabases.CreateIfNotExistsAsync(conn, TenantB); await conn.CloseAsync(); // Each run starts from sequence 0 so the assertions below are exact. @@ -170,27 +170,6 @@ private static long SequenceFor(System.Collections.Generic.IReadOnlyList statuses) => statuses.Single(x => x.ProjectionName == nameof(WidgetTally)).Shards.Single().EventStoreSequence; - private static async Task CreateDatabaseIfNotExists(NpgsqlConnection conn, string databaseName) - { - var builder = new NpgsqlConnectionStringBuilder(ConnectionSource.ConnectionString); - if (!await conn.DatabaseExists(databaseName)) - { - try - { - await new DatabaseSpecification().BuildDatabase(conn, databaseName); - } - catch (PostgresException e) when (e.SqlState is PostgresErrorCodes.DuplicateDatabase - or PostgresErrorCodes.UniqueViolation) - { - // The test assembly runs once per target framework, concurrently; the check-then-create above - // is not atomic, so the loser of that race sees the database it wanted already there. - } - } - - builder.Database = databaseName; - return builder.ConnectionString; - } - private static async Task DropSchemaAsync(string connectionString) { await using var conn = new NpgsqlConnection(connectionString); diff --git a/src/MultiTenancyTests/using_bucketed_database_sharding_and_document_partitioning.cs b/src/MultiTenancyTests/using_bucketed_database_sharding_and_document_partitioning.cs index bce758ef89..14f7b23f7f 100644 --- a/src/MultiTenancyTests/using_bucketed_database_sharding_and_document_partitioning.cs +++ b/src/MultiTenancyTests/using_bucketed_database_sharding_and_document_partitioning.cs @@ -26,7 +26,7 @@ namespace MultiTenancyTests; -[CollectionDefinition("multi-tenancy", DisableParallelization = true)] +[Collection("multi-tenancy")] public class using_bucketed_database_sharding_and_document_partitioning: IAsyncLifetime { private const int NumberOfPartitions = 4; @@ -40,20 +40,6 @@ public class using_bucketed_database_sharding_and_document_partitioning: IAsyncL private static readonly string TenantBeta = "tenant_beta"; private static readonly string TenantGamma = "tenant_gamma"; - private async Task CreateDatabaseIfNotExists(NpgsqlConnection conn, string databaseName) - { - var builder = new NpgsqlConnectionStringBuilder(ConnectionSource.ConnectionString); - - var exists = await conn.DatabaseExists(databaseName); - if (!exists) - { - await new DatabaseSpecification().BuildDatabase(conn, databaseName); - } - - builder.Database = databaseName; - return builder.ConnectionString; - } - public async ValueTask InitializeAsync() { await using var conn = new NpgsqlConnection(ConnectionSource.ConnectionString); @@ -64,7 +50,7 @@ public async ValueTask InitializeAsync() foreach (var name in _dbNames) { - _connectionStrings[name] = await CreateDatabaseIfNotExists(conn, name); + _connectionStrings[name] = await TenantDatabases.CreateIfNotExistsAsync(conn, name); } _registry = BucketRegistry.EvenlySpreadOver(_dbNames); diff --git a/src/MultiTenancyTests/using_master_table_multi_tenancy.cs b/src/MultiTenancyTests/using_master_table_multi_tenancy.cs index 3a2fb499ae..2cfd841900 100644 --- a/src/MultiTenancyTests/using_master_table_multi_tenancy.cs +++ b/src/MultiTenancyTests/using_master_table_multi_tenancy.cs @@ -22,21 +22,6 @@ namespace MultiTenancyTests; public class master_table_multi_tenancy_independent_auto_create { - private async Task CreateDatabaseIfNotExists(NpgsqlConnection conn, string databaseName) - { - var builder = new NpgsqlConnectionStringBuilder(ConnectionSource.ConnectionString); - - var exists = await conn.DatabaseExists(databaseName); - if (!exists) - { - await new DatabaseSpecification().BuildDatabase(conn, databaseName); - } - - builder.Database = databaseName; - - return builder.ConnectionString; - } - [Fact] public async Task can_still_create_own_tables() { @@ -45,10 +30,10 @@ public async Task can_still_create_own_tables() await conn.DropSchemaAsync("tenants", TestContext.Current.CancellationToken); - var tenant1ConnectionString = await CreateDatabaseIfNotExists(conn, "tenant1"); - var tenant2ConnectionString = await CreateDatabaseIfNotExists(conn, "tenant2"); - var tenant3ConnectionString = await CreateDatabaseIfNotExists(conn, "tenant3"); - var tenant4ConnectionString = await CreateDatabaseIfNotExists(conn, "tenant4"); + var tenant1ConnectionString = await TenantDatabases.CreateIfNotExistsAsync(conn, "tenant1"); + var tenant2ConnectionString = await TenantDatabases.CreateIfNotExistsAsync(conn, "tenant2"); + var tenant3ConnectionString = await TenantDatabases.CreateIfNotExistsAsync(conn, "tenant3"); + var tenant4ConnectionString = await TenantDatabases.CreateIfNotExistsAsync(conn, "tenant4"); await conn.CloseAsync(); @@ -86,7 +71,7 @@ public async Task can_still_create_own_tables() } -[CollectionDefinition("multi-tenancy", DisableParallelization = true)] +[Collection("multi-tenancy")] public class master_table_multi_tenancy_seeding : IAsyncLifetime { private IHost _host; @@ -96,21 +81,6 @@ public class master_table_multi_tenancy_seeding : IAsyncLifetime private string tenant3ConnectionString; private string tenant4ConnectionString; - private async Task CreateDatabaseIfNotExists(NpgsqlConnection conn, string databaseName) - { - var builder = new NpgsqlConnectionStringBuilder(ConnectionSource.ConnectionString); - - var exists = await conn.DatabaseExists(databaseName); - if (!exists) - { - await new DatabaseSpecification().BuildDatabase(conn, databaseName); - } - - builder.Database = databaseName; - - return builder.ConnectionString; - } - public async ValueTask InitializeAsync() { await using var conn = new NpgsqlConnection(ConnectionSource.ConnectionString); @@ -119,10 +89,10 @@ public async ValueTask InitializeAsync() await conn.DropSchemaAsync("tenants"); - tenant1ConnectionString = await CreateDatabaseIfNotExists(conn, "tenant1"); - tenant2ConnectionString = await CreateDatabaseIfNotExists(conn, "tenant2"); - tenant3ConnectionString = await CreateDatabaseIfNotExists(conn, "tenant3"); - tenant4ConnectionString = await CreateDatabaseIfNotExists(conn, "tenant4"); + tenant1ConnectionString = await TenantDatabases.CreateIfNotExistsAsync(conn, "tenant1"); + tenant2ConnectionString = await TenantDatabases.CreateIfNotExistsAsync(conn, "tenant2"); + tenant3ConnectionString = await TenantDatabases.CreateIfNotExistsAsync(conn, "tenant3"); + tenant4ConnectionString = await TenantDatabases.CreateIfNotExistsAsync(conn, "tenant4"); _host = await Host.CreateDefaultBuilder() .ConfigureServices(services => @@ -199,7 +169,7 @@ public async Task can_be_immediately_using_tenant_databases_that_were_seeded() } -[CollectionDefinition("multi-tenancy", DisableParallelization = true)] +[Collection("multi-tenancy")] public class using_master_table_multi_tenancy : IAsyncLifetime { private IHost _host; @@ -209,21 +179,6 @@ public class using_master_table_multi_tenancy : IAsyncLifetime private string tenant3ConnectionString; private string tenant4ConnectionString; - private async Task CreateDatabaseIfNotExists(NpgsqlConnection conn, string databaseName) - { - var builder = new NpgsqlConnectionStringBuilder(ConnectionSource.ConnectionString); - - var exists = await conn.DatabaseExists(databaseName); - if (!exists) - { - await new DatabaseSpecification().BuildDatabase(conn, databaseName); - } - - builder.Database = databaseName; - - return builder.ConnectionString; - } - public async ValueTask InitializeAsync() { await using var conn = new NpgsqlConnection(ConnectionSource.ConnectionString); @@ -232,10 +187,10 @@ public async ValueTask InitializeAsync() await conn.DropSchemaAsync("tenants"); - tenant1ConnectionString = await CreateDatabaseIfNotExists(conn, "tenant1"); - tenant2ConnectionString = await CreateDatabaseIfNotExists(conn, "tenant2"); - tenant3ConnectionString = await CreateDatabaseIfNotExists(conn, "tenant3"); - tenant4ConnectionString = await CreateDatabaseIfNotExists(conn, "tenant4"); + tenant1ConnectionString = await TenantDatabases.CreateIfNotExistsAsync(conn, "tenant1"); + tenant2ConnectionString = await TenantDatabases.CreateIfNotExistsAsync(conn, "tenant2"); + tenant3ConnectionString = await TenantDatabases.CreateIfNotExistsAsync(conn, "tenant3"); + tenant4ConnectionString = await TenantDatabases.CreateIfNotExistsAsync(conn, "tenant4"); _host = await Host.CreateDefaultBuilder() .ConfigureServices(services => @@ -282,10 +237,10 @@ public async Task run_describe() await conn.DropSchemaAsync("tenants", TestContext.Current.CancellationToken); - tenant1ConnectionString = await CreateDatabaseIfNotExists(conn, "tenant1"); - tenant2ConnectionString = await CreateDatabaseIfNotExists(conn, "tenant2"); - tenant3ConnectionString = await CreateDatabaseIfNotExists(conn, "tenant3"); - tenant4ConnectionString = await CreateDatabaseIfNotExists(conn, "tenant4"); + tenant1ConnectionString = await TenantDatabases.CreateIfNotExistsAsync(conn, "tenant1"); + tenant2ConnectionString = await TenantDatabases.CreateIfNotExistsAsync(conn, "tenant2"); + tenant3ConnectionString = await TenantDatabases.CreateIfNotExistsAsync(conn, "tenant3"); + tenant4ConnectionString = await TenantDatabases.CreateIfNotExistsAsync(conn, "tenant4"); var result = await Host.CreateDefaultBuilder() diff --git a/src/MultiTenancyTests/using_master_table_multi_tenancy_with_datasource.cs b/src/MultiTenancyTests/using_master_table_multi_tenancy_with_datasource.cs index e507151d2e..5839e8da8d 100644 --- a/src/MultiTenancyTests/using_master_table_multi_tenancy_with_datasource.cs +++ b/src/MultiTenancyTests/using_master_table_multi_tenancy_with_datasource.cs @@ -21,21 +21,6 @@ namespace MultiTenancyTests; public class master_table_multi_tenancy_independent_auto_create_with_datasource { - private async Task CreateDatabaseIfNotExists(NpgsqlConnection conn, string databaseName) - { - var builder = new NpgsqlConnectionStringBuilder(ConnectionSource.ConnectionString); - - var exists = await conn.DatabaseExists(databaseName); - if (!exists) - { - await new DatabaseSpecification().BuildDatabase(conn, databaseName); - } - - builder.Database = databaseName; - - return builder.ConnectionString; - } - [Fact] public async Task can_still_create_own_tables() { @@ -44,10 +29,10 @@ public async Task can_still_create_own_tables() await conn.DropSchemaAsync("tenants", TestContext.Current.CancellationToken); - var tenant1ConnectionString = await CreateDatabaseIfNotExists(conn, "tenant1"); - var tenant2ConnectionString = await CreateDatabaseIfNotExists(conn, "tenant2"); - var tenant3ConnectionString = await CreateDatabaseIfNotExists(conn, "tenant3"); - var tenant4ConnectionString = await CreateDatabaseIfNotExists(conn, "tenant4"); + var tenant1ConnectionString = await TenantDatabases.CreateIfNotExistsAsync(conn, "tenant1"); + var tenant2ConnectionString = await TenantDatabases.CreateIfNotExistsAsync(conn, "tenant2"); + var tenant3ConnectionString = await TenantDatabases.CreateIfNotExistsAsync(conn, "tenant3"); + var tenant4ConnectionString = await TenantDatabases.CreateIfNotExistsAsync(conn, "tenant4"); await conn.CloseAsync(); @@ -88,7 +73,7 @@ public async Task can_still_create_own_tables() } } -[CollectionDefinition("multi-tenancy", DisableParallelization = true)] +[Collection("multi-tenancy")] public class master_table_multi_tenancy_seeding_with_datasource: IAsyncLifetime { private IHost _host; @@ -98,21 +83,6 @@ public class master_table_multi_tenancy_seeding_with_datasource: IAsyncLifetime private string tenant3ConnectionString; private string tenant4ConnectionString; - private async Task CreateDatabaseIfNotExists(NpgsqlConnection conn, string databaseName) - { - var builder = new NpgsqlConnectionStringBuilder(ConnectionSource.ConnectionString); - - var exists = await conn.DatabaseExists(databaseName); - if (!exists) - { - await new DatabaseSpecification().BuildDatabase(conn, databaseName); - } - - builder.Database = databaseName; - - return builder.ConnectionString; - } - public async ValueTask InitializeAsync() { await using var conn = new NpgsqlConnection(ConnectionSource.ConnectionString); @@ -121,10 +91,10 @@ public async ValueTask InitializeAsync() await conn.DropSchemaAsync("tenants"); - tenant1ConnectionString = await CreateDatabaseIfNotExists(conn, "tenant1"); - tenant2ConnectionString = await CreateDatabaseIfNotExists(conn, "tenant2"); - tenant3ConnectionString = await CreateDatabaseIfNotExists(conn, "tenant3"); - tenant4ConnectionString = await CreateDatabaseIfNotExists(conn, "tenant4"); + tenant1ConnectionString = await TenantDatabases.CreateIfNotExistsAsync(conn, "tenant1"); + tenant2ConnectionString = await TenantDatabases.CreateIfNotExistsAsync(conn, "tenant2"); + tenant3ConnectionString = await TenantDatabases.CreateIfNotExistsAsync(conn, "tenant3"); + tenant4ConnectionString = await TenantDatabases.CreateIfNotExistsAsync(conn, "tenant4"); _host = await Host.CreateDefaultBuilder() .ConfigureServices(services => @@ -201,7 +171,7 @@ public async Task can_be_immediately_using_tenant_databases_that_were_seeded() } } -[CollectionDefinition("multi-tenancy", DisableParallelization = true)] +[Collection("multi-tenancy")] public class using_master_table_multi_tenancy_with_datasource: IAsyncLifetime { private IHost _host; @@ -211,21 +181,6 @@ public class using_master_table_multi_tenancy_with_datasource: IAsyncLifetime private string tenant3ConnectionString; private string tenant4ConnectionString; - private async Task CreateDatabaseIfNotExists(NpgsqlConnection conn, string databaseName) - { - var builder = new NpgsqlConnectionStringBuilder(ConnectionSource.ConnectionString); - - var exists = await conn.DatabaseExists(databaseName); - if (!exists) - { - await new DatabaseSpecification().BuildDatabase(conn, databaseName); - } - - builder.Database = databaseName; - - return builder.ConnectionString; - } - public async ValueTask InitializeAsync() { await using var conn = new NpgsqlConnection(ConnectionSource.ConnectionString); @@ -234,10 +189,10 @@ public async ValueTask InitializeAsync() await conn.DropSchemaAsync("tenants"); - tenant1ConnectionString = await CreateDatabaseIfNotExists(conn, "tenant1"); - tenant2ConnectionString = await CreateDatabaseIfNotExists(conn, "tenant2"); - tenant3ConnectionString = await CreateDatabaseIfNotExists(conn, "tenant3"); - tenant4ConnectionString = await CreateDatabaseIfNotExists(conn, "tenant4"); + tenant1ConnectionString = await TenantDatabases.CreateIfNotExistsAsync(conn, "tenant1"); + tenant2ConnectionString = await TenantDatabases.CreateIfNotExistsAsync(conn, "tenant2"); + tenant3ConnectionString = await TenantDatabases.CreateIfNotExistsAsync(conn, "tenant3"); + tenant4ConnectionString = await TenantDatabases.CreateIfNotExistsAsync(conn, "tenant4"); _host = await Host.CreateDefaultBuilder() .ConfigureServices(services => diff --git a/src/MultiTenancyTests/using_per_database_multitenancy.cs b/src/MultiTenancyTests/using_per_database_multitenancy.cs index e233d48b69..d84a20dc7a 100644 --- a/src/MultiTenancyTests/using_per_database_multitenancy.cs +++ b/src/MultiTenancyTests/using_per_database_multitenancy.cs @@ -21,7 +21,7 @@ namespace MultiTenancyTests; -[CollectionDefinition("multi-tenancy", DisableParallelization = true)] +[Collection("multi-tenancy")] public class using_per_database_multitenancy: IAsyncLifetime { private IHost _host; diff --git a/src/MultiTenancyTests/using_static_database_multitenancy.cs b/src/MultiTenancyTests/using_static_database_multitenancy.cs index f4601e9482..7ce0266583 100644 --- a/src/MultiTenancyTests/using_static_database_multitenancy.cs +++ b/src/MultiTenancyTests/using_static_database_multitenancy.cs @@ -19,35 +19,20 @@ namespace MultiTenancyTests; -[CollectionDefinition("multi-tenancy", DisableParallelization = true)] +[Collection("multi-tenancy")] public class using_static_database_multitenancy: IAsyncLifetime { private IHost _host; private IDocumentStore theStore; - private async Task CreateDatabaseIfNotExists(NpgsqlConnection conn, string databaseName) - { - var builder = new NpgsqlConnectionStringBuilder(ConnectionSource.ConnectionString); - - var exists = await conn.DatabaseExists(databaseName); - if (!exists) - { - await new DatabaseSpecification().BuildDatabase(conn, databaseName); - } - - builder.Database = databaseName; - - return builder.ConnectionString; - } - public async ValueTask InitializeAsync() { await using var conn = new NpgsqlConnection(ConnectionSource.ConnectionString); await conn.OpenAsync(); - var db1ConnectionString = await CreateDatabaseIfNotExists(conn, "database1"); - var tenant3ConnectionString = await CreateDatabaseIfNotExists(conn, "tenant3"); - var tenant4ConnectionString = await CreateDatabaseIfNotExists(conn, "tenant4"); + var db1ConnectionString = await TenantDatabases.CreateIfNotExistsAsync(conn, "database1"); + var tenant3ConnectionString = await TenantDatabases.CreateIfNotExistsAsync(conn, "tenant3"); + var tenant4ConnectionString = await TenantDatabases.CreateIfNotExistsAsync(conn, "tenant4"); #region sample_using_multi_tenanted_databases @@ -213,9 +198,9 @@ public async Task run_describe_with_single_document_store_and_static_multi_tenan await using var conn = new NpgsqlConnection(ConnectionSource.ConnectionString); await conn.OpenAsync(TestContext.Current.CancellationToken); - var db1ConnectionString = await CreateDatabaseIfNotExists(conn, "database1"); - var tenant3ConnectionString = await CreateDatabaseIfNotExists(conn, "tenant3"); - var tenant4ConnectionString = await CreateDatabaseIfNotExists(conn, "tenant4"); + var db1ConnectionString = await TenantDatabases.CreateIfNotExistsAsync(conn, "database1"); + var tenant3ConnectionString = await TenantDatabases.CreateIfNotExistsAsync(conn, "tenant3"); + var tenant4ConnectionString = await TenantDatabases.CreateIfNotExistsAsync(conn, "tenant4"); var result = await Host.CreateDefaultBuilder() .ConfigureServices(services =>