diff --git a/NDB.Test.Api/NDB.Test.Api.csproj b/NDB.Test.Api/NDB.Test.Api.csproj
index e913ef3..38f3053 100644
--- a/NDB.Test.Api/NDB.Test.Api.csproj
+++ b/NDB.Test.Api/NDB.Test.Api.csproj
@@ -4,6 +4,10 @@
net5.0
+
+
+
+
@@ -15,4 +19,22 @@
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+
diff --git a/NDB.Test.Api/Scripts/3.3.3/01.Test.sql b/NDB.Test.Api/Scripts/3.3.3/01.Test.sql
new file mode 100644
index 0000000..9f62ad7
--- /dev/null
+++ b/NDB.Test.Api/Scripts/3.3.3/01.Test.sql
@@ -0,0 +1 @@
+select 'Test script!'
\ No newline at end of file
diff --git a/NDB.Test.Api/Scripts/3.3.4/01.Test-new-ver.sql b/NDB.Test.Api/Scripts/3.3.4/01.Test-new-ver.sql
new file mode 100644
index 0000000..9f62ad7
--- /dev/null
+++ b/NDB.Test.Api/Scripts/3.3.4/01.Test-new-ver.sql
@@ -0,0 +1 @@
+select 'Test script!'
\ No newline at end of file
diff --git a/NDB.Test.Api/Scripts/3.3.4/02.Test-new-ver-2.sql b/NDB.Test.Api/Scripts/3.3.4/02.Test-new-ver-2.sql
new file mode 100644
index 0000000..9f62ad7
--- /dev/null
+++ b/NDB.Test.Api/Scripts/3.3.4/02.Test-new-ver-2.sql
@@ -0,0 +1 @@
+select 'Test script!'
\ No newline at end of file
diff --git a/NDB.Test.Api/Scripts/4.0.0/01.Major changes.sql b/NDB.Test.Api/Scripts/4.0.0/01.Major changes.sql
new file mode 100644
index 0000000..9f62ad7
--- /dev/null
+++ b/NDB.Test.Api/Scripts/4.0.0/01.Major changes.sql
@@ -0,0 +1 @@
+select 'Test script!'
\ No newline at end of file
diff --git a/NDB.Test.Api/Scripts/4.0.0/02.Last script.sql b/NDB.Test.Api/Scripts/4.0.0/02.Last script.sql
new file mode 100644
index 0000000..9f62ad7
--- /dev/null
+++ b/NDB.Test.Api/Scripts/4.0.0/02.Last script.sql
@@ -0,0 +1 @@
+select 'Test script!'
\ No newline at end of file
diff --git a/NDB.Test.Api/Startup.cs b/NDB.Test.Api/Startup.cs
index 7351f3d..9946f5e 100644
--- a/NDB.Test.Api/Startup.cs
+++ b/NDB.Test.Api/Startup.cs
@@ -3,10 +3,10 @@ using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
-using Microsoft.OpenApi.Models;
using NDB.Extensions.Swagger;
using NDB.Extensions.Swagger.Constants;
using NDB.Infrastructure.DatabaseMigration;
+using NDB.Infrastructure.DatabaseMigration.Constants;
using NDB.Test.Api.Extensions;
namespace NDB.Test.Api
@@ -29,7 +29,7 @@ namespace NDB.Test.Api
services.AddControllers();
services.AddSwagger("NDB.Test.Api", AuthorizationType.InhouseIdentity);
- services.AddMigration();
+ services.AddMigration(DatabaseType.SQLite, MetadataLocation.Database);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
diff --git a/NDB.Test.Api/appsettings.json b/NDB.Test.Api/appsettings.json
index c2c97f5..49bb0c0 100644
--- a/NDB.Test.Api/appsettings.json
+++ b/NDB.Test.Api/appsettings.json
@@ -1,7 +1,8 @@
{
"ConnectionStrings": {
- "DatabaseConnection": "Data Source={Workspace}\\TesterDb.db"
//"DatabaseConnection": "***REMOVED***"
+ //"DatabaseConnection": "***REMOVED***"
+ "DatabaseConnection": "Data Source={Workspace}\\NDB_TESTER.db"
},
"Logging": {
"LogLevel": {
diff --git a/infrastructure/NDB.Infrastructure.DatabaseMigration/Constants/ManifestResources.cs b/infrastructure/NDB.Infrastructure.DatabaseMigration/Constants/ManifestResources.cs
new file mode 100644
index 0000000..ab22360
--- /dev/null
+++ b/infrastructure/NDB.Infrastructure.DatabaseMigration/Constants/ManifestResources.cs
@@ -0,0 +1,16 @@
+namespace NDB.Infrastructure.DatabaseMigration.Constants
+{
+ internal struct ManifestResourcesPath
+ {
+ public const string
+ SqlServer = "NDB.Infrastructure.DatabaseMigration.Scripts.SqlServer.",
+ Sqlite = "NDB.Infrastructure.DatabaseMigration.Scripts.Sqlite.";
+ }
+
+ internal struct ManifestResources
+ {
+ public static string[]
+ SqlServer = new string[] { "01.CreateMigrationSchema.sql", "02.MigrationTables.sql" },
+ Sqlite = new string[] { "01.MigrationSignatureTable.sql", "02.MigratedVersionTable.sql", "03.MigratedScriptTable.sql" };
+ }
+}
diff --git a/infrastructure/NDB.Infrastructure.DatabaseMigration/Constants/MetadataLocation.cs b/infrastructure/NDB.Infrastructure.DatabaseMigration/Constants/MetadataLocation.cs
new file mode 100644
index 0000000..33b5843
--- /dev/null
+++ b/infrastructure/NDB.Infrastructure.DatabaseMigration/Constants/MetadataLocation.cs
@@ -0,0 +1,8 @@
+namespace NDB.Infrastructure.DatabaseMigration.Constants
+{
+ public enum MetadataLocation
+ {
+ XmlFile,
+ Database
+ }
+}
diff --git a/infrastructure/NDB.Infrastructure.DatabaseMigration/DbContexts/MigrationDbContext.cs b/infrastructure/NDB.Infrastructure.DatabaseMigration/DbContexts/MigrationDbContext.cs
index 7407ece..766bcd9 100644
--- a/infrastructure/NDB.Infrastructure.DatabaseMigration/DbContexts/MigrationDbContext.cs
+++ b/infrastructure/NDB.Infrastructure.DatabaseMigration/DbContexts/MigrationDbContext.cs
@@ -1,4 +1,6 @@
using Microsoft.EntityFrameworkCore;
+using NDB.Infrastructure.DatabaseMigration.Entities;
+using NDB.Infrastructure.DatabaseMigration.Entities.Configurations;
namespace NDB.Infrastructure.DatabaseMigration.DbContexts
{
@@ -8,9 +10,15 @@ namespace NDB.Infrastructure.DatabaseMigration.DbContexts
{
}
+ public DbSet MigrationSignatures { get; set; }
+
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
+
+ modelBuilder.ApplyConfiguration(new MigratedScriptConfiguration());
+ modelBuilder.ApplyConfiguration(new MigratedVersionConfiguration());
+ modelBuilder.ApplyConfiguration(new MigrationSignatureConfiguration());
}
}
}
diff --git a/infrastructure/NDB.Infrastructure.DatabaseMigration/DependencyInjectionExtensions.cs b/infrastructure/NDB.Infrastructure.DatabaseMigration/DependencyInjectionExtensions.cs
index ee89c47..2831485 100644
--- a/infrastructure/NDB.Infrastructure.DatabaseMigration/DependencyInjectionExtensions.cs
+++ b/infrastructure/NDB.Infrastructure.DatabaseMigration/DependencyInjectionExtensions.cs
@@ -7,6 +7,7 @@ using NDB.Infrastructure.DatabaseMigration.DbContexts;
using NDB.Infrastructure.DatabaseMigration.Models;
using NDB.Infrastructure.DatabaseMigration.Repositories;
using NDB.Infrastructure.DatabaseMigration.Services;
+using NDB.Infrastructure.DatabaseMigration.Services.Abstractions;
using System;
namespace NDB.Infrastructure.DatabaseMigration
@@ -17,13 +18,16 @@ namespace NDB.Infrastructure.DatabaseMigration
public static void AddMigration(this IServiceCollection services,
DatabaseType databaseType = DatabaseType.SQLite,
+ MetadataLocation metadataLocation = MetadataLocation.XmlFile,
string connectionName = "DatabaseConnection",
string workspace = "Workspace",
string scriptsDirectoryPath = "Scripts")
{
- var serviceConfiguration = new ServiceConfiguration(databaseType, connectionName, workspace, scriptsDirectoryPath);
+ var serviceConfiguration = new ServiceConfiguration(databaseType, metadataLocation, connectionName, workspace, scriptsDirectoryPath);
services.AddSingleton(serviceConfiguration);
services.AddDataAccess(serviceConfiguration);
+ services.AddSingleton();
+ services.AddSingleton();
services.AddSingleton();
}
diff --git a/infrastructure/NDB.Infrastructure.DatabaseMigration/Entities/Configurations/MigratedScriptConfiguration.cs b/infrastructure/NDB.Infrastructure.DatabaseMigration/Entities/Configurations/MigratedScriptConfiguration.cs
new file mode 100644
index 0000000..2f39818
--- /dev/null
+++ b/infrastructure/NDB.Infrastructure.DatabaseMigration/Entities/Configurations/MigratedScriptConfiguration.cs
@@ -0,0 +1,14 @@
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+
+namespace NDB.Infrastructure.DatabaseMigration.Entities.Configurations
+{
+ internal class MigratedScriptConfiguration : IEntityTypeConfiguration
+ {
+ public void Configure(EntityTypeBuilder builder)
+ {
+ builder.ToTable("MigratedScript", "migration").HasKey(z => z.Id);
+ builder.Property(z => z.Id).ValueGeneratedOnAdd();
+ }
+ }
+}
diff --git a/infrastructure/NDB.Infrastructure.DatabaseMigration/Entities/Configurations/MigratedVersionConfiguration.cs b/infrastructure/NDB.Infrastructure.DatabaseMigration/Entities/Configurations/MigratedVersionConfiguration.cs
new file mode 100644
index 0000000..81d46ba
--- /dev/null
+++ b/infrastructure/NDB.Infrastructure.DatabaseMigration/Entities/Configurations/MigratedVersionConfiguration.cs
@@ -0,0 +1,15 @@
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+
+namespace NDB.Infrastructure.DatabaseMigration.Entities.Configurations
+{
+ internal class MigratedVersionConfiguration : IEntityTypeConfiguration
+ {
+ public void Configure(EntityTypeBuilder builder)
+ {
+ builder.ToTable("MigratedVersion", "migration").HasKey(z => z.Id);
+ builder.Property(z => z.Id).ValueGeneratedOnAdd();
+ builder.HasMany(z => z.Scripts).WithOne().HasForeignKey(z => z.VersionId);
+ }
+ }
+}
diff --git a/infrastructure/NDB.Infrastructure.DatabaseMigration/Entities/Configurations/MigrationSignatureConfiguration.cs b/infrastructure/NDB.Infrastructure.DatabaseMigration/Entities/Configurations/MigrationSignatureConfiguration.cs
new file mode 100644
index 0000000..dbf9a1e
--- /dev/null
+++ b/infrastructure/NDB.Infrastructure.DatabaseMigration/Entities/Configurations/MigrationSignatureConfiguration.cs
@@ -0,0 +1,15 @@
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+
+namespace NDB.Infrastructure.DatabaseMigration.Entities.Configurations
+{
+ internal class MigrationSignatureConfiguration : IEntityTypeConfiguration
+ {
+ public void Configure(EntityTypeBuilder builder)
+ {
+ builder.ToTable("MigrationSignature", "migration").HasKey(z => z.Id);
+ builder.Property(z => z.Id).ValueGeneratedOnAdd();
+ builder.HasMany(z => z.MigratedVersions).WithOne().HasForeignKey(z => z.SignatureId);
+ }
+ }
+}
diff --git a/infrastructure/NDB.Infrastructure.DatabaseMigration/Entities/MigratedScript.cs b/infrastructure/NDB.Infrastructure.DatabaseMigration/Entities/MigratedScript.cs
new file mode 100644
index 0000000..2f9ea93
--- /dev/null
+++ b/infrastructure/NDB.Infrastructure.DatabaseMigration/Entities/MigratedScript.cs
@@ -0,0 +1,9 @@
+namespace NDB.Infrastructure.DatabaseMigration.Entities
+{
+ internal class MigratedScript
+ {
+ public int Id { get; set; }
+ public int VersionId { get; set; }
+ public string Script { get; set; }
+ }
+}
diff --git a/infrastructure/NDB.Infrastructure.DatabaseMigration/Entities/MigratedVersion.cs b/infrastructure/NDB.Infrastructure.DatabaseMigration/Entities/MigratedVersion.cs
new file mode 100644
index 0000000..35c016a
--- /dev/null
+++ b/infrastructure/NDB.Infrastructure.DatabaseMigration/Entities/MigratedVersion.cs
@@ -0,0 +1,13 @@
+using System.Collections.Generic;
+
+namespace NDB.Infrastructure.DatabaseMigration.Entities
+{
+ internal class MigratedVersion
+ {
+ public int Id { get; set; }
+ public int SignatureId { get; set; }
+ public string Version { get; set; }
+
+ public ICollection Scripts { get; set; }
+ }
+}
diff --git a/infrastructure/NDB.Infrastructure.DatabaseMigration/Entities/MigrationSignature.cs b/infrastructure/NDB.Infrastructure.DatabaseMigration/Entities/MigrationSignature.cs
new file mode 100644
index 0000000..4f7471f
--- /dev/null
+++ b/infrastructure/NDB.Infrastructure.DatabaseMigration/Entities/MigrationSignature.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+
+namespace NDB.Infrastructure.DatabaseMigration.Entities
+{
+ internal class MigrationSignature
+ {
+ public int Id { get; set; }
+ public DateTime MigrationDate { get; set; }
+ public string MachineName { get; set; }
+ public string LastVersion { get; set; }
+
+ public ICollection MigratedVersions { get; set; }
+ }
+}
diff --git a/infrastructure/NDB.Infrastructure.DatabaseMigration/Extensions/Mappings.cs b/infrastructure/NDB.Infrastructure.DatabaseMigration/Extensions/Mappings.cs
new file mode 100644
index 0000000..30f2aae
--- /dev/null
+++ b/infrastructure/NDB.Infrastructure.DatabaseMigration/Extensions/Mappings.cs
@@ -0,0 +1,49 @@
+using System.Linq;
+using e = NDB.Infrastructure.DatabaseMigration.Entities;
+using m = NDB.Infrastructure.DatabaseMigration.Models;
+
+namespace NDB.Infrastructure.DatabaseMigration.Extensions
+{
+ internal static class Mappings
+ {
+ public static m.MigratedVersion ToModel(this e.MigratedVersion migratedVersion)
+ {
+ return new m.MigratedVersion()
+ {
+ Version = migratedVersion.Version,
+ Scripts = migratedVersion.Scripts.Select(z => z.Script).ToArray()
+ };
+ }
+
+ public static m.MigrationSignature ToModel(this e.MigrationSignature migrationSignature)
+ {
+ return new m.MigrationSignature()
+ {
+ MigrationDate = migrationSignature.MigrationDate,
+ MachineName = migrationSignature.MachineName,
+ LastVersion = migrationSignature.LastVersion,
+ MigratedVersions = migrationSignature.MigratedVersions.Select(z => z.ToModel()).ToArray()
+ };
+ }
+
+ public static e.MigratedVersion ToEntity(this m.MigratedVersion migratedVersion)
+ {
+ return new e.MigratedVersion()
+ {
+ Version = migratedVersion.Version,
+ Scripts = migratedVersion.Scripts.Select(z => new e.MigratedScript() { Script = z }).ToArray()
+ };
+ }
+
+ public static e.MigrationSignature ToEntity(this m.MigrationSignature migrationSignature)
+ {
+ return new e.MigrationSignature()
+ {
+ MigrationDate = migrationSignature.MigrationDate,
+ MachineName = migrationSignature.MachineName,
+ LastVersion = migrationSignature.LastVersion,
+ MigratedVersions = migrationSignature.MigratedVersions.Select(z => z.ToEntity()).ToArray()
+ };
+ }
+ }
+}
diff --git a/infrastructure/NDB.Infrastructure.DatabaseMigration/Items/Examples/MigrationSignatures.xml b/infrastructure/NDB.Infrastructure.DatabaseMigration/Items/Examples/MigrationSignatures.xml
new file mode 100644
index 0000000..3385fa5
--- /dev/null
+++ b/infrastructure/NDB.Infrastructure.DatabaseMigration/Items/Examples/MigrationSignatures.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ 2022-02-10T20:20:42.2487339+02:00
+ TS1926
+
+
+ 1.0.0
+
+ 01.UserStatus table.sql
+ 02.AppUser table.sql
+ 03.IDX_AppUser_Email_NOTNULL.sql
+
+
+
+ 1.0.1
+
+ 01.UserClaim table.sql
+ 02.UserToken table.sql
+ 03.Add admin user.sql
+ 04.Add my user.sql
+
+
+
+ 1.0.1
+
+
+
\ No newline at end of file
diff --git a/infrastructure/NDB.Infrastructure.DatabaseMigration/Models/ServiceConfiguration.cs b/infrastructure/NDB.Infrastructure.DatabaseMigration/Models/ServiceConfiguration.cs
index 48a47c6..fdb6d73 100644
--- a/infrastructure/NDB.Infrastructure.DatabaseMigration/Models/ServiceConfiguration.cs
+++ b/infrastructure/NDB.Infrastructure.DatabaseMigration/Models/ServiceConfiguration.cs
@@ -5,13 +5,15 @@ namespace NDB.Infrastructure.DatabaseMigration.Models
internal class ServiceConfiguration
{
public DatabaseType DatabaseType { get; }
+ public MetadataLocation MetadataLocation { get; }
public string ConnectionName { get; }
public string Workspace { get; }
public string ScriptsDirectory { get; }
- public ServiceConfiguration(DatabaseType databaseType, string connectionName, string workspace, string scriptsDirectory)
+ public ServiceConfiguration(DatabaseType databaseType, MetadataLocation metadataLocation, string connectionName, string workspace, string scriptsDirectory)
{
DatabaseType = databaseType;
+ MetadataLocation = metadataLocation;
ConnectionName = connectionName;
Workspace = workspace;
ScriptsDirectory = scriptsDirectory;
diff --git a/infrastructure/NDB.Infrastructure.DatabaseMigration/NDB.Infrastructure.DatabaseMigration.csproj b/infrastructure/NDB.Infrastructure.DatabaseMigration/NDB.Infrastructure.DatabaseMigration.csproj
index bd4ee51..8b37f46 100644
--- a/infrastructure/NDB.Infrastructure.DatabaseMigration/NDB.Infrastructure.DatabaseMigration.csproj
+++ b/infrastructure/NDB.Infrastructure.DatabaseMigration/NDB.Infrastructure.DatabaseMigration.csproj
@@ -10,6 +10,22 @@
1.0.2
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/infrastructure/NDB.Infrastructure.DatabaseMigration/Repositories/IMigrationRepository.cs b/infrastructure/NDB.Infrastructure.DatabaseMigration/Repositories/IMigrationRepository.cs
index 76ef0df..6de68bd 100644
--- a/infrastructure/NDB.Infrastructure.DatabaseMigration/Repositories/IMigrationRepository.cs
+++ b/infrastructure/NDB.Infrastructure.DatabaseMigration/Repositories/IMigrationRepository.cs
@@ -1,9 +1,14 @@
-using System.Threading.Tasks;
+using NDB.Infrastructure.DatabaseMigration.Constants;
+using NDB.Infrastructure.DatabaseMigration.Entities;
+using System.Threading.Tasks;
namespace NDB.Infrastructure.DatabaseMigration.Repositories
{
- public interface IMigrationRepository
+ internal interface IMigrationRepository
{
Task ExecuteSqlRaw(string sqlRaw);
+ Task MigrationTablesAreSet(DatabaseType databaseType);
+ Task GetLastMigrationSignature();
+ Task AddMigrationSignature(MigrationSignature migrationSignature);
}
}
diff --git a/infrastructure/NDB.Infrastructure.DatabaseMigration/Repositories/MigrationRepository.cs b/infrastructure/NDB.Infrastructure.DatabaseMigration/Repositories/MigrationRepository.cs
index 97aa65c..329b48b 100644
--- a/infrastructure/NDB.Infrastructure.DatabaseMigration/Repositories/MigrationRepository.cs
+++ b/infrastructure/NDB.Infrastructure.DatabaseMigration/Repositories/MigrationRepository.cs
@@ -1,5 +1,9 @@
using Microsoft.EntityFrameworkCore;
+using NDB.Infrastructure.DatabaseMigration.Constants;
using NDB.Infrastructure.DatabaseMigration.DbContexts;
+using NDB.Infrastructure.DatabaseMigration.Entities;
+using System;
+using System.Linq;
using System.Threading.Tasks;
namespace NDB.Infrastructure.DatabaseMigration.Repositories
@@ -17,5 +21,40 @@ namespace NDB.Infrastructure.DatabaseMigration.Repositories
{
await _dbContext.Database.ExecuteSqlRawAsync(sqlRaw);
}
+
+ public async Task MigrationTablesAreSet(DatabaseType databaseType)
+ {
+ var query = databaseType switch
+ {
+ DatabaseType.SQLServer => "select count(1) from sys.objects where name = 'MigrationSignature' and type = 'U' and SCHEMA_NAME(schema_id)='migration'",
+ DatabaseType.SQLite => "select count(1) from sqlite_master where type='table' and name='MigrationSignature';",
+ _ => throw new NotImplementedException($"DatabaseMigration type {databaseType} is not implemented"),
+ };
+
+ using (var command = _dbContext.Database.GetDbConnection().CreateCommand())
+ {
+ command.CommandText = query;
+ await _dbContext.Database.OpenConnectionAsync();
+ var result = await command.ExecuteScalarAsync();
+
+ return result != null && result != DBNull.Value && Convert.ToInt32(result) > 0;
+ }
+ }
+
+ public Task GetLastMigrationSignature()
+ {
+ var query = _dbContext.MigrationSignatures
+ .Include(z => z.MigratedVersions).ThenInclude(z => z.Scripts)
+ .OrderByDescending(z => z.MigrationDate)
+ .AsSplitQuery();
+
+ return query.FirstOrDefaultAsync();
+ }
+
+ public async Task AddMigrationSignature(MigrationSignature migrationSignature)
+ {
+ await _dbContext.MigrationSignatures.AddAsync(migrationSignature);
+ await _dbContext.SaveChangesAsync();
+ }
}
}
diff --git a/infrastructure/NDB.Infrastructure.DatabaseMigration/Scripts/SqlServer/01.CreateMigrationSchema.sql b/infrastructure/NDB.Infrastructure.DatabaseMigration/Scripts/SqlServer/01.CreateMigrationSchema.sql
new file mode 100644
index 0000000..93ecca7
--- /dev/null
+++ b/infrastructure/NDB.Infrastructure.DatabaseMigration/Scripts/SqlServer/01.CreateMigrationSchema.sql
@@ -0,0 +1,4 @@
+if not exists (select top 1 1 from sys.schemas where name = 'migration')
+begin
+ EXEC ('CREATE SCHEMA [migration] AUTHORIZATION [dbo]')
+end
\ No newline at end of file
diff --git a/infrastructure/NDB.Infrastructure.DatabaseMigration/Scripts/SqlServer/02.MigrationTables.sql b/infrastructure/NDB.Infrastructure.DatabaseMigration/Scripts/SqlServer/02.MigrationTables.sql
new file mode 100644
index 0000000..5a76d52
--- /dev/null
+++ b/infrastructure/NDB.Infrastructure.DatabaseMigration/Scripts/SqlServer/02.MigrationTables.sql
@@ -0,0 +1,30 @@
+if not exists (select top 1 1 from sys.objects where name = 'MigrationSignature' and type = 'U' and SCHEMA_NAME(schema_id) = 'migration')
+begin
+ create table migration.MigrationSignature
+ (
+ Id int identity(1, 1) constraint PK_MigrationSignature primary key,
+ MigrationDate Datetime not null,
+ MachineName varchar(30) not null,
+ LastVersion varchar(20) not null
+ )
+end
+
+if not exists (select top 1 1 from sys.objects where name = 'MigratedVersion' and type = 'U' and SCHEMA_NAME(schema_id) = 'migration')
+begin
+ create table migration.MigratedVersion
+ (
+ Id int identity(1, 1) constraint PK_MigratedVersion primary key,
+ SignatureId int constraint FK_MigratedVersion_MigrationSignature foreign key references migration.MigrationSignature(Id),
+ [Version] varchar(20) not null
+ )
+end
+
+if not exists (select top 1 1 from sys.objects where name = 'MigratedScript' and type = 'U' and SCHEMA_NAME(schema_id) = 'migration')
+begin
+ create table migration.MigratedScript
+ (
+ Id int identity(1, 1) constraint PK_MigratedScript primary key,
+ VersionId int constraint FK_MigratedScript_MigratedVersion foreign key references migration.MigratedVersion(Id),
+ Script varchar(250) not null
+ )
+end
\ No newline at end of file
diff --git a/infrastructure/NDB.Infrastructure.DatabaseMigration/Scripts/Sqlite/01.MigrationSignatureTable.sql b/infrastructure/NDB.Infrastructure.DatabaseMigration/Scripts/Sqlite/01.MigrationSignatureTable.sql
new file mode 100644
index 0000000..fa68826
--- /dev/null
+++ b/infrastructure/NDB.Infrastructure.DatabaseMigration/Scripts/Sqlite/01.MigrationSignatureTable.sql
@@ -0,0 +1,7 @@
+CREATE TABLE "MigrationSignature" (
+ "Id" INTEGER NOT NULL,
+ "MigrationDate" TEXT NOT NULL,
+ "MachineName" TEXT NOT NULL,
+ "LastVersion" TEXT NOT NULL,
+ CONSTRAINT "PK_MigrationSignature" PRIMARY KEY("Id" AUTOINCREMENT)
+);
\ No newline at end of file
diff --git a/infrastructure/NDB.Infrastructure.DatabaseMigration/Scripts/Sqlite/02.MigratedVersionTable.sql b/infrastructure/NDB.Infrastructure.DatabaseMigration/Scripts/Sqlite/02.MigratedVersionTable.sql
new file mode 100644
index 0000000..e799cd7
--- /dev/null
+++ b/infrastructure/NDB.Infrastructure.DatabaseMigration/Scripts/Sqlite/02.MigratedVersionTable.sql
@@ -0,0 +1,7 @@
+CREATE TABLE "MigratedVersion" (
+ "Id" INTEGER NOT NULL,
+ "SignatureId" INTEGER,
+ "Version" TEXT NOT NULL,
+ CONSTRAINT "PK_MigratedVersion" PRIMARY KEY("Id" AUTOINCREMENT),
+ CONSTRAINT "FK_MigratedVersion_MigrationSignature" FOREIGN KEY("SignatureId") REFERENCES "MigrationSignature"("Id")
+);
\ No newline at end of file
diff --git a/infrastructure/NDB.Infrastructure.DatabaseMigration/Scripts/Sqlite/03.MigratedScriptTable.sql b/infrastructure/NDB.Infrastructure.DatabaseMigration/Scripts/Sqlite/03.MigratedScriptTable.sql
new file mode 100644
index 0000000..3dd59e8
--- /dev/null
+++ b/infrastructure/NDB.Infrastructure.DatabaseMigration/Scripts/Sqlite/03.MigratedScriptTable.sql
@@ -0,0 +1,7 @@
+CREATE TABLE "MigratedScript" (
+ "Id" INTEGER NOT NULL,
+ "VersionId" INTEGER,
+ "Script" TEXT NOT NULL,
+ CONSTRAINT "PK_MigratedScript" PRIMARY KEY("Id" AUTOINCREMENT),
+ CONSTRAINT "FK_MigratedScript_MigratedVersion" FOREIGN KEY("VersionId") REFERENCES "MigratedVersion"("Id")
+);
\ No newline at end of file
diff --git a/infrastructure/NDB.Infrastructure.DatabaseMigration/Services/Abstractions/IMetadataLocationService.cs b/infrastructure/NDB.Infrastructure.DatabaseMigration/Services/Abstractions/IMetadataLocationService.cs
new file mode 100644
index 0000000..f3ac067
--- /dev/null
+++ b/infrastructure/NDB.Infrastructure.DatabaseMigration/Services/Abstractions/IMetadataLocationService.cs
@@ -0,0 +1,9 @@
+using System.Threading.Tasks;
+
+namespace NDB.Infrastructure.DatabaseMigration.Services.Abstractions
+{
+ internal interface IMetadataLocationService
+ {
+ Task Check();
+ }
+}
diff --git a/infrastructure/NDB.Infrastructure.DatabaseMigration/Services/IMigrationService.cs b/infrastructure/NDB.Infrastructure.DatabaseMigration/Services/Abstractions/IMigrationService.cs
similarity index 54%
rename from infrastructure/NDB.Infrastructure.DatabaseMigration/Services/IMigrationService.cs
rename to infrastructure/NDB.Infrastructure.DatabaseMigration/Services/Abstractions/IMigrationService.cs
index 0c69a61..463875a 100644
--- a/infrastructure/NDB.Infrastructure.DatabaseMigration/Services/IMigrationService.cs
+++ b/infrastructure/NDB.Infrastructure.DatabaseMigration/Services/Abstractions/IMigrationService.cs
@@ -1,4 +1,4 @@
-namespace NDB.Infrastructure.DatabaseMigration.Services
+namespace NDB.Infrastructure.DatabaseMigration.Services.Abstractions
{
public interface IMigrationService
{
diff --git a/infrastructure/NDB.Infrastructure.DatabaseMigration/Services/Abstractions/IMigrationSignaturesService.cs b/infrastructure/NDB.Infrastructure.DatabaseMigration/Services/Abstractions/IMigrationSignaturesService.cs
new file mode 100644
index 0000000..7868b5e
--- /dev/null
+++ b/infrastructure/NDB.Infrastructure.DatabaseMigration/Services/Abstractions/IMigrationSignaturesService.cs
@@ -0,0 +1,11 @@
+using NDB.Infrastructure.DatabaseMigration.Models;
+using System.Threading.Tasks;
+
+namespace NDB.Infrastructure.DatabaseMigration.Services.Abstractions
+{
+ internal interface IMigrationSignaturesService
+ {
+ Task GetLastMigrationSignature();
+ Task SaveMigrationSignature(MigrationSignature migrationSignature);
+ }
+}
diff --git a/infrastructure/NDB.Infrastructure.DatabaseMigration/Services/MetadataLocationService.cs b/infrastructure/NDB.Infrastructure.DatabaseMigration/Services/MetadataLocationService.cs
new file mode 100644
index 0000000..13012f3
--- /dev/null
+++ b/infrastructure/NDB.Infrastructure.DatabaseMigration/Services/MetadataLocationService.cs
@@ -0,0 +1,100 @@
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using NDB.Infrastructure.DatabaseMigration.Constants;
+using NDB.Infrastructure.DatabaseMigration.Models;
+using NDB.Infrastructure.DatabaseMigration.Repositories;
+using NDB.Infrastructure.DatabaseMigration.Services.Abstractions;
+using System;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Threading.Tasks;
+
+namespace NDB.Infrastructure.DatabaseMigration.Services
+{
+ internal class MetadataLocationService : IMetadataLocationService
+ {
+ private readonly ServiceConfiguration _configuration;
+ private readonly IServiceProvider _serviceProvider;
+ private readonly ILogger _logger;
+
+ public MetadataLocationService(ServiceConfiguration configuration, IServiceProvider serviceProvider, ILogger logger)
+ {
+ _configuration=configuration;
+ _serviceProvider=serviceProvider;
+ _logger=logger;
+ }
+
+ public async Task Check()
+ {
+ switch (_configuration.MetadataLocation)
+ {
+ case MetadataLocation.XmlFile:
+ CheckWorkspace();
+ break;
+ case MetadataLocation.Database:
+ await CheckMigrationTables();
+ break;
+ default:
+ throw new NotImplementedException($"Metadata location {_configuration.MetadataLocation} is not implemented.");
+ }
+ }
+
+ private void CheckWorkspace()
+ {
+ if (string.IsNullOrEmpty(_configuration.Workspace))
+ throw new Exception($"Workspace path is empty! Check 'Workspace' parameter.");
+
+ if (!Directory.Exists(_configuration.Workspace))
+ Directory.CreateDirectory(_configuration.Workspace);
+ }
+
+ private async Task CheckMigrationTables()
+ {
+ using (var scope = _serviceProvider.CreateScope())
+ {
+ var _repository = scope.ServiceProvider.GetRequiredService();
+ var migrationTablesAreSet = await _repository.MigrationTablesAreSet(_configuration.DatabaseType);
+ if (migrationTablesAreSet)
+ return;
+
+ var assembly = Assembly.GetExecutingAssembly();
+ var allEmbeddedResources = assembly.GetManifestResourceNames();
+
+ var sqlResources = GetSqlResources();
+ foreach (var resource in sqlResources)
+ {
+ if (!allEmbeddedResources.Contains(resource))
+ {
+ _logger.LogWarning($"Manifest resource {resource} does not exists in assembly {assembly.FullName} and will be ignored.");
+ continue;
+ }
+
+ var resourceContent = GetManifestResourceContent(assembly, resource);
+ await _repository.ExecuteSqlRaw(resourceContent);
+ }
+ }
+ }
+
+ private string[] GetSqlResources()
+ {
+ var result = _configuration.DatabaseType switch
+ {
+ DatabaseType.SQLServer => ManifestResources.SqlServer.Select(resource => $"{ManifestResourcesPath.SqlServer}{resource}"),
+ DatabaseType.SQLite => ManifestResources.Sqlite.Select(resource => $"{ManifestResourcesPath.Sqlite}{resource}"),
+ _ => throw new NotImplementedException($"DatabaseMigration type {_configuration.DatabaseType} is not implemented"),
+ };
+ return result.ToArray();
+ }
+
+ private string GetManifestResourceContent(Assembly assembly, string resourceName)
+ {
+ using (Stream stream = assembly.GetManifestResourceStream(resourceName))
+ using (StreamReader reader = new StreamReader(stream))
+ {
+ string result = reader.ReadToEnd();
+ return result;
+ }
+ }
+ }
+}
diff --git a/infrastructure/NDB.Infrastructure.DatabaseMigration/Services/MigrationService.cs b/infrastructure/NDB.Infrastructure.DatabaseMigration/Services/MigrationService.cs
index 4efd680..de09dea 100644
--- a/infrastructure/NDB.Infrastructure.DatabaseMigration/Services/MigrationService.cs
+++ b/infrastructure/NDB.Infrastructure.DatabaseMigration/Services/MigrationService.cs
@@ -2,73 +2,42 @@
using Microsoft.Extensions.Logging;
using NDB.Infrastructure.DatabaseMigration.Models;
using NDB.Infrastructure.DatabaseMigration.Repositories;
+using NDB.Infrastructure.DatabaseMigration.Services.Abstractions;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
-using System.Xml;
-using System.Xml.Serialization;
namespace NDB.Infrastructure.DatabaseMigration.Services
{
internal class MigrationService : IMigrationService
{
- private readonly string _migrationSignaturesFilePath;
- private const string _migrationSignaturesFileName = "MigrationSignatures.xml";
private readonly ILogger _logger;
private readonly IServiceProvider _serviceProvider;
private readonly ServiceConfiguration _configuration;
+ private readonly IMetadataLocationService _metadataLocationService;
+ private readonly IMigrationSignaturesService _migrationSignaturesService;
- public MigrationService(ILogger logger, IServiceProvider serviceProvider, ServiceConfiguration configuration)
+ public MigrationService(ILogger logger, IServiceProvider serviceProvider, ServiceConfiguration configuration, IMetadataLocationService metadataLocationService, IMigrationSignaturesService migrationSignaturesService)
{
- _migrationSignaturesFilePath = Path.Combine(configuration.Workspace, _migrationSignaturesFileName);
_logger = logger;
_serviceProvider = serviceProvider;
_configuration = configuration;
+ _metadataLocationService = metadataLocationService;
+ _migrationSignaturesService = migrationSignaturesService;
}
- private void CheckWorkspace()
- {
- if (string.IsNullOrEmpty(_configuration.Workspace))
- throw new Exception($"Workspace path is empty! Check 'Workspace' parameter.");
+ public void Execute() => ExecuteAsync().GetAwaiter().GetResult();
- if (!Directory.Exists(_configuration.Workspace))
- Directory.CreateDirectory(_configuration.Workspace);
- }
-
- private MigrationSignature[] GetMigrationSignatures()
- {
- if (!File.Exists(_migrationSignaturesFilePath))
- return null;
-
- var serializer = new XmlSerializer(typeof(MigrationThumbprint));
- using (var reader = XmlReader.Create(_migrationSignaturesFilePath))
- {
- var migrationSignatureRoot = (MigrationThumbprint)serializer.Deserialize(reader);
- return migrationSignatureRoot.MigrationSignatures;
- }
- }
-
- private void SaveMigrationSignatures(MigrationSignature[] migrationSignatures)
- {
- var root = new MigrationThumbprint() { MigrationSignatures = migrationSignatures };
- var serializer = new XmlSerializer(root.GetType());
- var settings = new XmlWriterSettings() { Indent = true };
- using (var writer = XmlWriter.Create(_migrationSignaturesFilePath, settings))
- {
- serializer.Serialize(writer, root);
- }
- }
-
- public void Execute()
+ private async Task ExecuteAsync()
{
_logger.LogInformation("Starting migration...");
- CheckWorkspace();
+ await _metadataLocationService.Check();
- var localSignatures = GetMigrationSignatures();
- var lastInstalledVersion = localSignatures?.OrderByDescending(z => z.MigrationDate).FirstOrDefault()?.LastVersion ?? "0.0.0";
+ var lastSignature = await _migrationSignaturesService.GetLastMigrationSignature();
+ var lastInstalledVersion = lastSignature?.LastVersion ?? "0.0.0";
var targetVersion = new Version(lastInstalledVersion);
var scriptPacks = GetScriptPacks();
var packsToInstall = scriptPacks.Where(p => p.Version > targetVersion);
@@ -90,7 +59,9 @@ namespace NDB.Infrastructure.DatabaseMigration.Services
_logger.LogInformation($"Running script pack: '{pack.Version}'");
- Array.ForEach(scripts, s => RunScript(s));
+ foreach (var script in scripts)
+ await RunScript(script);
+
var migratedVersion = new MigratedVersion() { Version = pack.Version.ToString(), Scripts = scripts.Select(z => Path.GetFileName(z)).ToArray() };
migratedVersions.Add(migratedVersion);
}
@@ -98,10 +69,7 @@ namespace NDB.Infrastructure.DatabaseMigration.Services
signature.MigratedVersions = migratedVersions.ToArray();
signature.LastVersion = packsToInstall.OrderByDescending(z => z.Version).First().Version.ToString();
- var signatures = localSignatures != null ? new List(localSignatures) : new List();
- signatures.Add(signature);
-
- SaveMigrationSignatures(signatures.ToArray());
+ await _migrationSignaturesService.SaveMigrationSignature(signature);
}
private ScriptPack[] GetScriptPacks()
@@ -112,10 +80,7 @@ namespace NDB.Infrastructure.DatabaseMigration.Services
return packs.ToArray();
}
- private void RunScript(string path)
- => RunScriptAsync(path).GetAwaiter().GetResult();
-
- private async Task RunScriptAsync(string path)
+ private async Task RunScript(string path)
{
_logger.LogInformation($"Running sql script: '{path}'");
var sqlContent = File.ReadAllText(path);
diff --git a/infrastructure/NDB.Infrastructure.DatabaseMigration/Services/MigrationSignaturesService.cs b/infrastructure/NDB.Infrastructure.DatabaseMigration/Services/MigrationSignaturesService.cs
new file mode 100644
index 0000000..85a17ab
--- /dev/null
+++ b/infrastructure/NDB.Infrastructure.DatabaseMigration/Services/MigrationSignaturesService.cs
@@ -0,0 +1,125 @@
+using Microsoft.Extensions.DependencyInjection;
+using NDB.Infrastructure.DatabaseMigration.Constants;
+using NDB.Infrastructure.DatabaseMigration.Extensions;
+using NDB.Infrastructure.DatabaseMigration.Models;
+using NDB.Infrastructure.DatabaseMigration.Repositories;
+using NDB.Infrastructure.DatabaseMigration.Services.Abstractions;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Xml;
+using System.Xml.Serialization;
+
+namespace NDB.Infrastructure.DatabaseMigration.Services
+{
+ internal class MigrationSignaturesService : IMigrationSignaturesService
+ {
+ private const string _migrationSignaturesFileName = "MigrationSignatures.xml";
+ private readonly string _migrationSignaturesFilePath;
+ private readonly ServiceConfiguration _configuration;
+ private readonly IServiceProvider _serviceProvider;
+
+ private MigrationThumbprint Thumbprint;
+
+ public MigrationSignaturesService(ServiceConfiguration configuration, IServiceProvider serviceProvider)
+ {
+ _migrationSignaturesFilePath = Path.Combine(configuration.Workspace, _migrationSignaturesFileName);
+ _configuration = configuration;
+ _serviceProvider = serviceProvider;
+ }
+
+ public async Task GetLastMigrationSignature()
+ {
+ switch (_configuration.MetadataLocation)
+ {
+ case MetadataLocation.XmlFile:
+ return GetLastMigrationSignatureFromFile();
+ case MetadataLocation.Database:
+ return await GetLastMigrationSignatureFromDatabase();
+ default:
+ throw new NotImplementedException($"Metadata location {_configuration.MetadataLocation} is not implemented.");
+ }
+ }
+
+ public async Task SaveMigrationSignature(MigrationSignature migrationSignature)
+ {
+ switch (_configuration.MetadataLocation)
+ {
+ case MetadataLocation.XmlFile:
+ SaveMigrationSignatureToFile(migrationSignature);
+ break;
+ case MetadataLocation.Database:
+ await SaveMigrationSignatureToDatabase(migrationSignature);
+ break;
+ default:
+ throw new NotImplementedException($"Metadata location {_configuration.MetadataLocation} is not implemented.");
+ }
+ }
+
+ private MigrationSignature GetLastMigrationSignatureFromFile()
+ {
+ Thumbprint = GetMigrationThumbprintFromFile();
+ var lastSignature = Thumbprint?.MigrationSignatures?.OrderByDescending(z => z.MigrationDate).FirstOrDefault();
+ return lastSignature;
+ }
+
+ private MigrationThumbprint GetMigrationThumbprintFromFile()
+ {
+ if (!File.Exists(_migrationSignaturesFilePath))
+ return null;
+
+ var serializer = new XmlSerializer(typeof(MigrationThumbprint));
+ using (var reader = XmlReader.Create(_migrationSignaturesFilePath))
+ {
+ var migrationSignatureRoot = (MigrationThumbprint)serializer.Deserialize(reader);
+ return migrationSignatureRoot;
+ }
+ }
+
+ private void SaveMigrationSignatureToFile(MigrationSignature migrationSignature)
+ {
+ if (Thumbprint != null)
+ {
+ var migrationSignatures = new List(Thumbprint.MigrationSignatures) { migrationSignature };
+ Thumbprint.MigrationSignatures = migrationSignatures.ToArray();
+ }
+ else
+ {
+ Thumbprint = new MigrationThumbprint()
+ {
+ MigrationSignatures = new List() { migrationSignature }.ToArray()
+ };
+ }
+
+ var serializer = new XmlSerializer(Thumbprint.GetType());
+ var settings = new XmlWriterSettings() { Indent = true };
+ using (var writer = XmlWriter.Create(_migrationSignaturesFilePath, settings))
+ {
+ serializer.Serialize(writer, Thumbprint);
+ }
+ }
+
+ private async Task GetLastMigrationSignatureFromDatabase()
+ {
+ using (var scope = _serviceProvider.CreateScope())
+ {
+ var _repository = scope.ServiceProvider.GetRequiredService();
+ var lastMigrationSignature = await _repository.GetLastMigrationSignature();
+ if (lastMigrationSignature == null)
+ return null;
+ return lastMigrationSignature.ToModel();
+ }
+ }
+
+ private async Task SaveMigrationSignatureToDatabase(MigrationSignature migrationSignature)
+ {
+ using (var scope = _serviceProvider.CreateScope())
+ {
+ var _repository = scope.ServiceProvider.GetRequiredService();
+ await _repository.AddMigrationSignature(migrationSignature.ToEntity());
+ }
+ }
+ }
+}