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
Tudor Stanciu 2022-02-16 17:09:06 +00:00
parent 92632050d7
commit f9bb922c01
35 changed files with 609 additions and 59 deletions

View File

@ -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>

View File

@ -0,0 +1 @@
select 'Test script!'

View File

@ -0,0 +1 @@
select 'Test script!'

View File

@ -0,0 +1 @@
select 'Test script!'

View File

@ -0,0 +1 @@
select 'Test script!'

View File

@ -0,0 +1 @@
select 'Test script!'

View File

@ -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.

View File

@ -1,7 +1,8 @@
{
"ConnectionStrings": {
"DatabaseConnection": "Data Source={Workspace}\\TesterDb.db"
//"DatabaseConnection": "***REMOVED***"
//"DatabaseConnection": "***REMOVED***"
"DatabaseConnection": "Data Source={Workspace}\\NDB_TESTER.db"
},
"Logging": {
"LogLevel": {

View File

@ -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" };
}
}

View File

@ -0,0 +1,8 @@
namespace NDB.Infrastructure.DatabaseMigration.Constants
{
public enum MetadataLocation
{
XmlFile,
Database
}
}

View File

@ -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());
}
}
}

View File

@ -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>();
}

View File

@ -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();
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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; }
}
}

View File

@ -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; }
}
}

View File

@ -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; }
}
}

View File

@ -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()
};
}
}
}

View File

@ -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>

View File

@ -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;

View File

@ -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" />

View File

@ -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);
}
}

View File

@ -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();
}
}
}

View File

@ -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

View File

@ -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

View File

@ -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)
);

View File

@ -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")
);

View File

@ -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")
);

View File

@ -0,0 +1,9 @@
using System.Threading.Tasks;
namespace NDB.Infrastructure.DatabaseMigration.Services.Abstractions
{
internal interface IMetadataLocationService
{
Task Check();
}
}

View File

@ -1,4 +1,4 @@
namespace NDB.Infrastructure.DatabaseMigration.Services
namespace NDB.Infrastructure.DatabaseMigration.Services.Abstractions
{
public interface IMigrationService
{

View File

@ -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);
}
}

View File

@ -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;
}
}
}
}

View File

@ -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);

View File

@ -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());
}
}
}
}