using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using NDB.Infrastructure.DatabaseMigration.Models; using NDB.Infrastructure.DatabaseMigration.Repositories; using System; using System.Collections.Generic; using System.IO; using System.Linq; 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; public MigrationService(ILogger logger, IServiceProvider serviceProvider, ServiceConfiguration configuration) { _migrationSignaturesFilePath = Path.Combine(configuration.Workspace, _migrationSignaturesFileName); _logger = logger; _serviceProvider = serviceProvider; _configuration = configuration; } 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 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() { _logger.LogInformation("Starting migration..."); CheckWorkspace(); var localSignatures = GetMigrationSignatures(); var lastInstalledVersion = localSignatures?.OrderByDescending(z => z.MigrationDate).FirstOrDefault()?.LastVersion ?? "0.0.0"; var targetVersion = new Version(lastInstalledVersion); var scriptPacks = GetScriptPacks(); var packsToInstall = scriptPacks.Where(p => p.Version > targetVersion); if (!packsToInstall.Any()) { _logger.LogInformation("Nothing to migrate."); return; } var signature = new MigrationSignature() { MachineName = System.Environment.MachineName, MigrationDate = DateTime.Now }; var migratedVersions = new List(); foreach (var pack in packsToInstall.OrderBy(p => p.Version)) { var scripts = Directory.GetFiles(pack.Path); if (!scripts.Any()) continue; _logger.LogInformation($"Running script pack: '{pack.Version}'"); Array.ForEach(scripts, s => RunScript(s)); var migratedVersion = new MigratedVersion() { Version = pack.Version.ToString(), Scripts = scripts.Select(z => Path.GetFileName(z)).ToArray() }; migratedVersions.Add(migratedVersion); } 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()); } private ScriptPack[] GetScriptPacks() { var scripts = Directory.GetDirectories(_configuration.ScriptsDirectory); var packs = scripts.Select(z => new ScriptPack() { Path = z, Version = new Version(new DirectoryInfo(z).Name) }); return packs.ToArray(); } private void RunScript(string path) { _logger.LogInformation($"Running sql script: '{path}'"); var sqlContent = File.ReadAllText(path); if (string.IsNullOrEmpty(sqlContent)) return; using (var scope = _serviceProvider.CreateScope()) { var _repository = scope.ServiceProvider.GetRequiredService(); _repository.ExecuteSqlRaw(sqlContent); } } } }