From b129fe37cea91b04cd7627ec6e8eb0d53c0df117 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stano=20Pe=C5=A5ko?= Date: Sun, 28 Jul 2019 18:37:13 +0200 Subject: [PATCH] Migrations middleware supports multiple databases --- README.md | 40 ++++++++++++------- src/DatabaseFactory.cs | 15 ++++++- src/IDatabaseFactory.cs | 23 ++++++++++- src/KormBuilder.cs | 26 ++++++------ src/Kros.KORM.Extensions.Asp.csproj | 2 +- .../Middleware/MigrationMiddlewareOptions.cs | 4 +- .../Middleware/MigrationsMiddleware.cs | 39 ++++++++++-------- tests/KormBuilderShould.cs | 28 ------------- 8 files changed, 99 insertions(+), 78 deletions(-) diff --git a/README.md b/README.md index b88223c..5d946eb 100644 --- a/README.md +++ b/README.md @@ -89,33 +89,43 @@ CONSTRAINT [PK_People] PRIMARY KEY CLUSTERED ([Id] ASC) GO ``` -Migration can also be executed through an HTTP request. By calling the `/kormmigration` endpoint, the necessary migrations will be executed. -However, you need to add middleware: +Migration can also be executed through an HTTP request. By calling the `/kormmigration` endpoint, the necessary migrations will be executed. However, you need to add middleware: ``` csharp public void Configure(IApplicationBuilder app, IHostingEnvironment env) { - app.UseKormMigrations(o => - { - o.EndpointUrl = "/kormmigration"; - }); + app.UseKormMigrations(); } ``` +You can change the endpoint URL by configuration: + +``` csharp +public void Configure(IApplicationBuilder app, IHostingEnvironment env) +{ + app.UseKormMigrations(options => + { + options.EndpointUrl = "/loremipsum"; + }); +} +``` + +If multiple KORM databases are registered, all of them have unique name. Migrations are performed per database and the name of the database is specified in URL as another path segment: `/kormmigration/dbname` If the name is not specified, default connection string will be used (`DefaultConnection`). + If you have scripts stored in a different way (for example, somewhere on a disk or in another assembly), you can configure your own providers to get these scripts. ``` csharp public void ConfigureServices(IServiceCollection services) { - services.AddKorm(Configuration) - .AddKormMigrations(o => - { - var assembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(x => x.FullName.StartWith("Demo.DatabaseLayer")); - o.AddAssemblyScriptsProvider(assembly, "Demo.DatabaseLayer.Resources"); - o.AddFileScriptsProvider(@"C:\scripts\"); - o.AddScriptsProvider(new MyCustomScriptsProvider()); - }) - .Migrate(); + services.AddKorm(Configuration) + .AddKormMigrations(o => + { + var assembly = AppDomain.CurrentDomain.GetAssemblies() .FirstOrDefault(x => x.FullName.StartWith ("Demo.DatabaseLayer") ); + o.AddAssemblyScriptsProvider(assembly, "Demo.DatabaseLayer.Resources"); + o.AddFileScriptsProvider(@"C:\scripts\"); + o.AddScriptsProvider(new MyCustomScriptsProvider()); + }) + .Migrate(); } ``` diff --git a/src/DatabaseFactory.cs b/src/DatabaseFactory.cs index a5080ad..589e7b8 100644 --- a/src/DatabaseFactory.cs +++ b/src/DatabaseFactory.cs @@ -1,4 +1,5 @@ using Kros.KORM.Extensions.Asp.Properties; +using Kros.KORM.Migrations; using Kros.Utils; using Microsoft.Extensions.DependencyInjection; using System; @@ -43,6 +44,18 @@ internal DatabaseFactory(IServiceCollection services) } IDatabase IDatabaseFactory.GetDatabase(string name) + { + KormBuilder builder = GetBuilder(name); + return _databases.GetOrAdd(name, _ => builder.Build()); + } + + IMigrationsRunner IDatabaseFactory.GetMigrationsRunner(string name) + { + KormBuilder builder = GetBuilder(name); + return builder.MigrationsRunner; + } + + private KormBuilder GetBuilder(string name) { if (_disposed) { @@ -56,7 +69,7 @@ IDatabase IDatabaseFactory.GetDatabase(string name) string.Format(Resources.InvalidDatabaseName, name, nameof(ServiceCollectionExtensions.AddKorm)), nameof(name)); } - return _databases.GetOrAdd(name, _ => builder.Build()); + return builder; } public void Dispose() diff --git a/src/IDatabaseFactory.cs b/src/IDatabaseFactory.cs index 45dde2d..ae38083 100644 --- a/src/IDatabaseFactory.cs +++ b/src/IDatabaseFactory.cs @@ -1,4 +1,5 @@ -using System; +using Kros.KORM.Migrations; +using System; namespace Kros.KORM.Extensions.Asp { @@ -22,5 +23,25 @@ public interface IDatabaseFactory : IDisposable /// /// IDatabase GetDatabase(string name); + + /// + /// Returns the migrations runner for database with specified . + /// + /// Name of the database. + /// Implementation of or . + /// + /// Migrations must be added by . + /// If migrations was not added for specified database, this method returns . + /// + /// The value of is . + /// + /// The value of is: + /// + /// Empty string. + /// String containing whitespace characters. + /// Ivalid name. The database with that name is not registered. + /// + /// + IMigrationsRunner GetMigrationsRunner(string name); } } diff --git a/src/KormBuilder.cs b/src/KormBuilder.cs index f1dc9ab..2d9c9e3 100644 --- a/src/KormBuilder.cs +++ b/src/KormBuilder.cs @@ -20,6 +20,7 @@ public class KormBuilder public const string DefaultConnectionStringName = "DefaultConnection"; private readonly IDatabaseBuilder _builder; + private IMigrationsRunner _migrationsRunner; /// /// Initializes a new instance of the class. @@ -52,6 +53,12 @@ public KormBuilder(IServiceCollection services, KormConnectionSettings connectio /// public IServiceCollection Services { get; } + /// + /// for this database, if it was set + /// using method. + /// + public IMigrationsRunner MigrationsRunner => _migrationsRunner; + internal KormConnectionSettings ConnectionSettings { get; } /// @@ -98,15 +105,9 @@ public KormBuilder InitDatabaseForIdGenerator() /// This instance of . public KormBuilder AddKormMigrations(Action setupAction = null) { - Services - .AddMemoryCache() - .AddTransient((Func)(s => - { - var database = new Database(ConnectionSettings); - MigrationOptions options = SetupMigrationOptions(setupAction); - return new MigrationsRunner(database, options); - })); - + Services.AddMemoryCache(); + MigrationOptions options = SetupMigrationOptions(setupAction); + _migrationsRunner = new MigrationsRunner(ConnectionSettings.GetFullConnectionString(), options); return this; } @@ -131,12 +132,9 @@ private static MigrationOptions SetupMigrationOptions(Action s /// public void Migrate() { - if (ConnectionSettings.AutoMigrate) + if (ConnectionSettings.AutoMigrate && (MigrationsRunner != null)) { - Services.BuildServiceProvider() - .GetService() - .MigrateAsync() - .Wait(); + MigrationsRunner.MigrateAsync().Wait(); } } diff --git a/src/Kros.KORM.Extensions.Asp.csproj b/src/Kros.KORM.Extensions.Asp.csproj index 46f3d2c..291b116 100644 --- a/src/Kros.KORM.Extensions.Asp.csproj +++ b/src/Kros.KORM.Extensions.Asp.csproj @@ -30,7 +30,7 @@ - + diff --git a/src/Migrations/Middleware/MigrationMiddlewareOptions.cs b/src/Migrations/Middleware/MigrationMiddlewareOptions.cs index 7a3adc0..f3786fd 100644 --- a/src/Migrations/Middleware/MigrationMiddlewareOptions.cs +++ b/src/Migrations/Middleware/MigrationMiddlewareOptions.cs @@ -8,9 +8,9 @@ namespace Kros.KORM.Migrations.Middleware public class MigrationMiddlewareOptions { /// - /// Migrations endpoint URL. + /// Migrations endpoint URL. Default value is /kormmigration. /// - public string EndpointUrl { get; set; } = "/kormmigrate"; + public string EndpointUrl { get; set; } = "/kormmigration"; /// /// Minimum time between two migrations. diff --git a/src/Migrations/Middleware/MigrationsMiddleware.cs b/src/Migrations/Middleware/MigrationsMiddleware.cs index 809c158..6fe720c 100644 --- a/src/Migrations/Middleware/MigrationsMiddleware.cs +++ b/src/Migrations/Middleware/MigrationsMiddleware.cs @@ -1,4 +1,5 @@ -using Kros.Utils; +using Kros.KORM.Extensions.Asp; +using Kros.Utils; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Caching.Memory; using System.Threading.Tasks; @@ -10,7 +11,7 @@ namespace Kros.KORM.Migrations.Middleware /// public class MigrationsMiddleware { - private const string WasMigrationExecutedKey = "WasMigrationExecuted"; + private const string MigrationExecutedKey = "KormMigrationsExecuted"; #pragma warning disable IDE0052 // Remove unread private members private readonly RequestDelegate _next; @@ -24,40 +25,46 @@ public class MigrationsMiddleware /// The next delegate. /// Memory cache. /// Migration options. - public MigrationsMiddleware( - RequestDelegate next, - IMemoryCache cache, - MigrationMiddlewareOptions options) + public MigrationsMiddleware(RequestDelegate next, IMemoryCache cache, MigrationMiddlewareOptions options) { _next = next; _options = Check.NotNull(options, nameof(options)); _cache = Check.NotNull(cache, nameof(cache)); } -#pragma warning disable IDE0060 // Remove unused parameter /// /// Invokes the specified context. /// /// The context. - /// Migrations runner. - public async Task Invoke(HttpContext context, IMigrationsRunner migrationsRunner) + /// Database factory for getting . + public async Task Invoke(HttpContext context, IDatabaseFactory databaseFactory) { - if (CanMigrate()) + string databaseName = null; + if (context.Request.Path.HasValue) { - SetupCache(); + databaseName = context.Request.Path.Value.Trim('/'); + } + if (string.IsNullOrEmpty(databaseName)) + { + databaseName = KormBuilder.DefaultConnectionStringName; + } + IMigrationsRunner migrationsRunner = databaseFactory.GetMigrationsRunner(databaseName); + if ((migrationsRunner != null) && CanMigrate(databaseName)) + { + SetupCache(databaseName); await migrationsRunner.MigrateAsync(); } } -#pragma warning restore IDE0060 // Remove unused parameter - private bool CanMigrate() - => !_cache.TryGetValue(WasMigrationExecutedKey, out bool migrated) || !migrated; + private bool CanMigrate(string name) => !_cache.TryGetValue(GetCacheKey(name), out bool migrated) || !migrated; - private void SetupCache() + private void SetupCache(string name) { var options = new MemoryCacheEntryOptions(); options.SetSlidingExpiration(_options.SlidingExpirationBetweenMigrations); - _cache.Set(WasMigrationExecutedKey, true, options); + _cache.Set(GetCacheKey(name), true, options); } + + private string GetCacheKey(string name) => MigrationExecutedKey + "-" + name; } } diff --git a/tests/KormBuilderShould.cs b/tests/KormBuilderShould.cs index ace5edd..c40c83a 100644 --- a/tests/KormBuilderShould.cs +++ b/tests/KormBuilderShould.cs @@ -1,7 +1,6 @@ using FluentAssertions; using Kros.KORM.Extensions.Asp; using Kros.KORM.Metadata; -using Kros.KORM.Migrations; using Microsoft.Extensions.DependencyInjection; using NSubstitute; using System; @@ -33,33 +32,6 @@ public void ThrowArgumentExceptionWhenArgumentsAreInvalid() action.Should().Throw().And.ParamName.Should().Be("connectionString"); } - [Fact] - public void AddMigrationsToContainer() - { - KormBuilder kormBuilder = CreateKormBuilder(false); - kormBuilder.AddKormMigrations(); - - kormBuilder.Services.BuildServiceProvider() - .GetService() - .Should().NotBeNull(); - } - - [Theory] - [InlineData(true, 1)] - [InlineData(false, 0)] - public void ExecuteMigrationsBasedOnAutoMigrateValue(bool autoMigrate, int migrateCallCount) - { - KormBuilder kormBuilder = CreateKormBuilder(autoMigrate); - kormBuilder.AddKormMigrations(); - - IMigrationsRunner migrationRunner = Substitute.For(); - kormBuilder.Services.AddSingleton(migrationRunner); - - kormBuilder.Migrate(); - - migrationRunner.Received(migrateCallCount).MigrateAsync(); - } - [Fact] public void UseDatabaseConfiguration() {