Skip to content

Commit

Permalink
Migrations middleware supports multiple databases
Browse files Browse the repository at this point in the history
  • Loading branch information
satano committed Jul 28, 2019
1 parent 3441b6b commit b129fe3
Show file tree
Hide file tree
Showing 8 changed files with 99 additions and 78 deletions.
40 changes: 25 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
```

Expand Down
15 changes: 14 additions & 1 deletion src/DatabaseFactory.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Kros.KORM.Extensions.Asp.Properties;
using Kros.KORM.Migrations;
using Kros.Utils;
using Microsoft.Extensions.DependencyInjection;
using System;
Expand Down Expand Up @@ -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)
{
Expand All @@ -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()
Expand Down
23 changes: 22 additions & 1 deletion src/IDatabaseFactory.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using Kros.KORM.Migrations;
using System;

namespace Kros.KORM.Extensions.Asp
{
Expand All @@ -22,5 +23,25 @@ public interface IDatabaseFactory : IDisposable
/// </list>
/// </exception>
IDatabase GetDatabase(string name);

/// <summary>
/// Returns the migrations runner for database with specified <paramref name="name"/>.
/// </summary>
/// <param name="name">Name of the database.</param>
/// <returns>Implementation of <see cref="IMigrationsRunner"/> or <see langword="null"/>.</returns>
/// <remarks>
/// Migrations must be added by <see cref="KormBuilder.AddKormMigrations(Action{MigrationOptions})"/>.
/// If migrations was not added for specified database, this method returns <see langword="null"/>.
/// </remarks>
/// <exception cref="ArgumentNullException">The value of <paramref name="name"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentException">
/// The value of <paramref name="name"/> is:
/// <list type="bullet">
/// <item>Empty string.</item>
/// <item>String containing whitespace characters.</item>
/// <item>Ivalid name. The database with that name is not registered.</item>
/// </list>
/// </exception>
IMigrationsRunner GetMigrationsRunner(string name);
}
}
26 changes: 12 additions & 14 deletions src/KormBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public class KormBuilder
public const string DefaultConnectionStringName = "DefaultConnection";

private readonly IDatabaseBuilder _builder;
private IMigrationsRunner _migrationsRunner;

/// <summary>
/// Initializes a new instance of the <see cref="KormBuilder"/> class.
Expand Down Expand Up @@ -52,6 +53,12 @@ public KormBuilder(IServiceCollection services, KormConnectionSettings connectio
/// </summary>
public IServiceCollection Services { get; }

/// <summary>
/// <see cref="MigrationsRunner"/> for this database, if it was set
/// using <see cref="AddKormMigrations(Action{MigrationOptions})"/> method.
/// </summary>
public IMigrationsRunner MigrationsRunner => _migrationsRunner;

internal KormConnectionSettings ConnectionSettings { get; }

/// <summary>
Expand Down Expand Up @@ -98,15 +105,9 @@ public KormBuilder InitDatabaseForIdGenerator()
/// <returns>This instance of <see cref="KormBuilder"/>.</returns>
public KormBuilder AddKormMigrations(Action<MigrationOptions> setupAction = null)
{
Services
.AddMemoryCache()
.AddTransient((Func<IServiceProvider, IMigrationsRunner>)(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;
}

Expand All @@ -131,12 +132,9 @@ private static MigrationOptions SetupMigrationOptions(Action<MigrationOptions> s
/// </summary>
public void Migrate()
{
if (ConnectionSettings.AutoMigrate)
if (ConnectionSettings.AutoMigrate && (MigrationsRunner != null))
{
Services.BuildServiceProvider()
.GetService<IMigrationsRunner>()
.MigrateAsync()
.Wait();
MigrationsRunner.MigrateAsync().Wait();
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/Kros.KORM.Extensions.Asp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Kros.KORM" Version="4.0.0-alpha.6" />
<PackageReference Include="Kros.KORM" Version="4.0.0-alpha.8" />
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.2.2" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="2.2.0" />
Expand Down
4 changes: 2 additions & 2 deletions src/Migrations/Middleware/MigrationMiddlewareOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ namespace Kros.KORM.Migrations.Middleware
public class MigrationMiddlewareOptions
{
/// <summary>
/// Migrations endpoint URL.
/// Migrations endpoint URL. Default value is <c>/kormmigration</c>.
/// </summary>
public string EndpointUrl { get; set; } = "/kormmigrate";
public string EndpointUrl { get; set; } = "/kormmigration";

/// <summary>
/// Minimum time between two migrations.
Expand Down
39 changes: 23 additions & 16 deletions src/Migrations/Middleware/MigrationsMiddleware.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -10,7 +11,7 @@ namespace Kros.KORM.Migrations.Middleware
/// </summary>
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;
Expand All @@ -24,40 +25,46 @@ public class MigrationsMiddleware
/// <param name="next">The next delegate.</param>
/// <param name="cache">Memory cache.</param>
/// <param name="options">Migration options.</param>
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
/// <summary>
/// Invokes the specified context.
/// </summary>
/// <param name="context">The context.</param>
/// <param name="migrationsRunner">Migrations runner.</param>
public async Task Invoke(HttpContext context, IMigrationsRunner migrationsRunner)
/// <param name="databaseFactory">Database factory for getting <see cref="IMigrationsRunner"/>.</param>
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;
}
}
28 changes: 0 additions & 28 deletions tests/KormBuilderShould.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -33,33 +32,6 @@ public void ThrowArgumentExceptionWhenArgumentsAreInvalid()
action.Should().Throw<ArgumentException>().And.ParamName.Should().Be("connectionString");
}

[Fact]
public void AddMigrationsToContainer()
{
KormBuilder kormBuilder = CreateKormBuilder(false);
kormBuilder.AddKormMigrations();

kormBuilder.Services.BuildServiceProvider()
.GetService<IMigrationsRunner>()
.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<IMigrationsRunner>();
kormBuilder.Services.AddSingleton(migrationRunner);

kormBuilder.Migrate();

migrationRunner.Received(migrateCallCount).MigrateAsync();
}

[Fact]
public void UseDatabaseConfiguration()
{
Expand Down

0 comments on commit b129fe3

Please sign in to comment.