Merged PR 35: Migration service can keep its metadata from now in multiple locations: XmlFile or Database (SqlServer or Sqlite)
- Database metadata location - added migration tables - MigrationSignaturesService - MigrationSignaturesService - Migration service can keep its metadata from now in multiple locations: XmlFile or Database (SqlServer or Sqlite)messaging
parent
92632050d7
commit
f9bb922c01
|
@ -4,6 +4,10 @@
|
|||
<TargetFramework>net5.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="Scripts\01.Test.sql" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.6.3" />
|
||||
</ItemGroup>
|
||||
|
@ -15,4 +19,22 @@
|
|||
<ProjectReference Include="..\test\NDB.Test.Application\NDB.Test.Application.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="Scripts\3.3.3\01.Test.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Scripts\3.3.4\01.Test-new-ver.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Scripts\3.3.4\02.Test-new-ver-2.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Scripts\4.0.0\01.Major changes.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Scripts\4.0.0\02.Last script.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
select 'Test script!'
|
|
@ -0,0 +1 @@
|
|||
select 'Test script!'
|
|
@ -0,0 +1 @@
|
|||
select 'Test script!'
|
|
@ -0,0 +1 @@
|
|||
select 'Test script!'
|
|
@ -0,0 +1 @@
|
|||
select 'Test script!'
|
|
@ -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.
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
{
|
||||
"ConnectionStrings": {
|
||||
"DatabaseConnection": "Data Source={Workspace}\\TesterDb.db"
|
||||
//"DatabaseConnection": "***REMOVED***"
|
||||
//"DatabaseConnection": "***REMOVED***"
|
||||
"DatabaseConnection": "Data Source={Workspace}\\NDB_TESTER.db"
|
||||
},
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
|
|
|
@ -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" };
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
namespace NDB.Infrastructure.DatabaseMigration.Constants
|
||||
{
|
||||
public enum MetadataLocation
|
||||
{
|
||||
XmlFile,
|
||||
Database
|
||||
}
|
||||
}
|
|
@ -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<MigrationSignature> MigrationSignatures { get; set; }
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
base.OnModelCreating(modelBuilder);
|
||||
|
||||
modelBuilder.ApplyConfiguration(new MigratedScriptConfiguration());
|
||||
modelBuilder.ApplyConfiguration(new MigratedVersionConfiguration());
|
||||
modelBuilder.ApplyConfiguration(new MigrationSignatureConfiguration());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<IMetadataLocationService, MetadataLocationService>();
|
||||
services.AddSingleton<IMigrationSignaturesService, MigrationSignaturesService>();
|
||||
services.AddSingleton<IMigrationService, MigrationService>();
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace NDB.Infrastructure.DatabaseMigration.Entities.Configurations
|
||||
{
|
||||
internal class MigratedScriptConfiguration : IEntityTypeConfiguration<MigratedScript>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<MigratedScript> builder)
|
||||
{
|
||||
builder.ToTable("MigratedScript", "migration").HasKey(z => z.Id);
|
||||
builder.Property(z => z.Id).ValueGeneratedOnAdd();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace NDB.Infrastructure.DatabaseMigration.Entities.Configurations
|
||||
{
|
||||
internal class MigratedVersionConfiguration : IEntityTypeConfiguration<MigratedVersion>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<MigratedVersion> 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace NDB.Infrastructure.DatabaseMigration.Entities.Configurations
|
||||
{
|
||||
internal class MigrationSignatureConfiguration : IEntityTypeConfiguration<MigrationSignature>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<MigrationSignature> 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
}
|
||||
}
|
|
@ -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<MigratedScript> Scripts { get; set; }
|
||||
}
|
||||
}
|
|
@ -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<MigratedVersion> MigratedVersions { get; set; }
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<MigrationThumbprint xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
|
||||
<MigrationSignatures>
|
||||
<MigrationSignature>
|
||||
<MigrationDate>2022-02-10T20:20:42.2487339+02:00</MigrationDate>
|
||||
<MachineName>TS1926</MachineName>
|
||||
<MigratedVersions>
|
||||
<MigratedVersion>
|
||||
<Version>1.0.0</Version>
|
||||
<Scripts>
|
||||
<string>01.UserStatus table.sql</string>
|
||||
<string>02.AppUser table.sql</string>
|
||||
<string>03.IDX_AppUser_Email_NOTNULL.sql</string>
|
||||
</Scripts>
|
||||
</MigratedVersion>
|
||||
<MigratedVersion>
|
||||
<Version>1.0.1</Version>
|
||||
<Scripts>
|
||||
<string>01.UserClaim table.sql</string>
|
||||
<string>02.UserToken table.sql</string>
|
||||
<string>03.Add admin user.sql</string>
|
||||
<string>04.Add my user.sql</string>
|
||||
</Scripts>
|
||||
</MigratedVersion>
|
||||
</MigratedVersions>
|
||||
<LastVersion>1.0.1</LastVersion>
|
||||
</MigrationSignature>
|
||||
</MigrationSignatures>
|
||||
</MigrationThumbprint>
|
|
@ -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;
|
||||
|
|
|
@ -10,6 +10,22 @@
|
|||
<Version>1.0.2</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="Scripts\Sqlite\01.MigrationSignatureTable.sql" />
|
||||
<None Remove="Scripts\Sqlite\02.MigratedVersionTable.sql" />
|
||||
<None Remove="Scripts\Sqlite\03.MigratedScriptTable.sql" />
|
||||
<None Remove="Scripts\SqlServer\01.CreateMigrationSchema.sql" />
|
||||
<None Remove="Scripts\SqlServer\02.MigrationTables.sql" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Scripts\Sqlite\03.MigratedScriptTable.sql" />
|
||||
<EmbeddedResource Include="Scripts\Sqlite\02.MigratedVersionTable.sql" />
|
||||
<EmbeddedResource Include="Scripts\Sqlite\01.MigrationSignatureTable.sql" />
|
||||
<EmbeddedResource Include="Scripts\SqlServer\02.MigrationTables.sql" />
|
||||
<EmbeddedResource Include="Scripts\SqlServer\01.CreateMigrationSchema.sql" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.13" />
|
||||
|
|
|
@ -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<bool> MigrationTablesAreSet(DatabaseType databaseType);
|
||||
Task<MigrationSignature> GetLastMigrationSignature();
|
||||
Task AddMigrationSignature(MigrationSignature migrationSignature);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<bool> 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<MigrationSignature> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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)
|
||||
);
|
|
@ -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")
|
||||
);
|
|
@ -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")
|
||||
);
|
|
@ -0,0 +1,9 @@
|
|||
using System.Threading.Tasks;
|
||||
|
||||
namespace NDB.Infrastructure.DatabaseMigration.Services.Abstractions
|
||||
{
|
||||
internal interface IMetadataLocationService
|
||||
{
|
||||
Task Check();
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
namespace NDB.Infrastructure.DatabaseMigration.Services
|
||||
namespace NDB.Infrastructure.DatabaseMigration.Services.Abstractions
|
||||
{
|
||||
public interface IMigrationService
|
||||
{
|
|
@ -0,0 +1,11 @@
|
|||
using NDB.Infrastructure.DatabaseMigration.Models;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace NDB.Infrastructure.DatabaseMigration.Services.Abstractions
|
||||
{
|
||||
internal interface IMigrationSignaturesService
|
||||
{
|
||||
Task<MigrationSignature> GetLastMigrationSignature();
|
||||
Task SaveMigrationSignature(MigrationSignature migrationSignature);
|
||||
}
|
||||
}
|
|
@ -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<MetadataLocationService> _logger;
|
||||
|
||||
public MetadataLocationService(ServiceConfiguration configuration, IServiceProvider serviceProvider, ILogger<MetadataLocationService> 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<IMigrationRepository>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<MigrationService> _logger;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly ServiceConfiguration _configuration;
|
||||
private readonly IMetadataLocationService _metadataLocationService;
|
||||
private readonly IMigrationSignaturesService _migrationSignaturesService;
|
||||
|
||||
public MigrationService(ILogger<MigrationService> logger, IServiceProvider serviceProvider, ServiceConfiguration configuration)
|
||||
public MigrationService(ILogger<MigrationService> 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<MigrationSignature>(localSignatures) : new List<MigrationSignature>();
|
||||
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);
|
||||
|
|
|
@ -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<MigrationSignature> 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<MigrationSignature>(Thumbprint.MigrationSignatures) { migrationSignature };
|
||||
Thumbprint.MigrationSignatures = migrationSignatures.ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
Thumbprint = new MigrationThumbprint()
|
||||
{
|
||||
MigrationSignatures = new List<MigrationSignature>() { 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<MigrationSignature> GetLastMigrationSignatureFromDatabase()
|
||||
{
|
||||
using (var scope = _serviceProvider.CreateScope())
|
||||
{
|
||||
var _repository = scope.ServiceProvider.GetRequiredService<IMigrationRepository>();
|
||||
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<IMigrationRepository>();
|
||||
await _repository.AddMigrationSignature(migrationSignature.ToEntity());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue