using System.Diagnostics; using DataModel.Database; using DataModel.Database.Common; using DataModel.MigrationScripts; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; namespace DataModel; public static class Setup { private const string DB_READ_WRITE_MODE = "ReadWrite"; private const string DB_READ_WRITE_CREATE_MODE = "ReadWriteCreate"; private static string USED_DATA_FILE = string.Empty; public static SetupMaintenance SETUP_MAINTENANCE = new(); public static string DataFile => Setup.USED_DATA_FILE; /// /// Tries to migrate the database. /// public static async Task PerformDataMigration(DataContext dbContext) { var pendingMigrations = (await dbContext.Database.GetPendingMigrationsAsync()).ToList(); foreach (var pendingMigration in pendingMigrations) { Console.WriteLine($"The migration '{pendingMigration}' is pending."); } await dbContext.Database.MigrateAsync(); // // Post migration actions: // if (pendingMigrations.Contains("20221106193544_202211AddUniqueIds")) await Script202211AddUniqueIds.PostMigrationAsync(dbContext); } /// /// Imports a JSON file into a new database. /// public static async Task ImportDataAndAddDatabase(this IServiceCollection serviceCollection, string path2JSONFile) { Console.WriteLine($"Importing the data from the JSON file '{path2JSONFile}' into a new database."); var tempPath = Path.GetTempFileName(); Console.WriteLine($"The temporary database file is: {tempPath}"); serviceCollection.AddDbContext(options => options.UseSqlite($"Filename={tempPath};Mode={DB_READ_WRITE_CREATE_MODE};"), ServiceLifetime.Transient); // Get the database service: await using var serviceProvider = serviceCollection.BuildServiceProvider(); var dbContext = serviceProvider.GetRequiredService(); Setup.USED_DATA_FILE = tempPath; Setup.SETUP_MAINTENANCE = new(tempPath, true); // Migrate the database to create the tables etc.: await Setup.PerformDataMigration(dbContext); // Next, we import the data from the provided JSON file: await dbContext.ImportAsync(path2JSONFile); // // Next, we enable the auto-export feature to keep the source file up to date. // The auto-export feature might exist, but we enforce it, when we work with a // temporary database source by a JSON file. // // // Enable the auto-export feature: // if (await dbContext.Settings.FirstOrDefaultAsync(n => n.Code == SettingNames.AUTO_EXPORT_ENABLED) is { } autoExportEnabled) autoExportEnabled.BoolValue = true; else dbContext.Settings.Add(new Setting {Code = SettingNames.AUTO_EXPORT_ENABLED, BoolValue = true}); // // Set the auto-export path and file: // if(await dbContext.Settings.FirstOrDefaultAsync(n => n.Code == SettingNames.AUTO_EXPORT_DESTINATION_PATH) is { } autoExportPath) autoExportPath.TextValue = Path.GetDirectoryName(path2JSONFile) ?? string.Empty; else dbContext.Settings.Add(new Setting {Code = SettingNames.AUTO_EXPORT_DESTINATION_PATH, TextValue = Path.GetDirectoryName(path2JSONFile) ?? string.Empty}); if(await dbContext.Settings.FirstOrDefaultAsync(n => n.Code == SettingNames.AUTO_EXPORT_FILENAME) is { } autoExportFile) autoExportFile.TextValue = Path.GetFileName(path2JSONFile); else dbContext.Settings.Add(new Setting {Code = SettingNames.AUTO_EXPORT_FILENAME, TextValue = Path.GetFileName(path2JSONFile)}); // Ensure that the sensitive data setting is present and disabled by default: var _ = await dbContext.Settings.FirstOrDefaultAsync(n => n.Code == SettingNames.AUTO_EXPORT_SENSITIVE_DATA) ?? new Setting { Code = SettingNames.AUTO_EXPORT_SENSITIVE_DATA, BoolValue = false, }; // Save the changes: await dbContext.SaveChangesAsync(); } /// /// Creates and adds the database instance to the DI system (extension method). /// public static void AddDatabase(this IServiceCollection serviceCollection, string path2DataFile, bool createWhenNecessary = true) { Setup.USED_DATA_FILE = path2DataFile; Setup.SETUP_MAINTENANCE = new(path2DataFile, false); serviceCollection.AddDbContext(options => options.UseSqlite($"Filename={path2DataFile};Mode={(createWhenNecessary ? DB_READ_WRITE_CREATE_MODE : DB_READ_WRITE_MODE)};"), ServiceLifetime.Transient); } /// /// Create the database instance from the given path. Used for the EF tooling. /// public static DataContext CreateDatabaseInstance4Tooling(string path2DataFile, bool createWhenNecessary = true) { // Store the path to the database: Setup.USED_DATA_FILE = path2DataFile; Setup.SETUP_MAINTENANCE = new(path2DataFile, false); // Create a database builder: var builder = new DbContextOptionsBuilder(); // Add the database configuration to the builder: builder.UseSqlite($"Filename={path2DataFile};Mode={(createWhenNecessary ? DB_READ_WRITE_CREATE_MODE : DB_READ_WRITE_MODE)};"); // Next, construct the database context: var dbContext = new DataContext(builder.Options); return dbContext; } public readonly record struct SetupMaintenance(string PathToDataFile = "", bool RemoveTempDatabaseAfterwards = false) : IDisposable { public void Dispose() { if (!this.RemoveTempDatabaseAfterwards) return; Console.WriteLine("Removing the temporary database file..."); try { var process = new Process { StartInfo = new() { FileName = "cmd.exe", Arguments = $"""/C del /Q /F "{Setup.SETUP_MAINTENANCE.PathToDataFile}" """, UseShellExecute = false, CreateNoWindow = true, } }; process.Start(); Console.WriteLine($"The temporary database file '{this.PathToDataFile}' has been removed."); } catch(Exception e) { Console.WriteLine($"Failed to remove the temporary database file: {e.Message} // {e.InnerException?.Message}"); } } } }