GFlisch / Arc4u.Guidance.Doc

Other
5 stars 1 forks source link

Integrate Migrationtool in Arc4u #105

Open janschmidmaier50 opened 1 year ago

janschmidmaier50 commented 1 year ago

As far as I have seen there is a Migrationtool for every Service (having an EFCore project) being created, implementing always the same structure, like interpreting the command line arguments.

Those things should be put into Arc4u. This way there will be no code duplications, plus improvements of the migrationtool will be available to all projects.

So maybe something like that would be feasible

public class MigrationApplicationConfiguration
{
    public string? ConnectionStringName { get; set; }
    public string? MigrationHistoryTable { get; set; }
    public string? MigrationHistoryAssembly { get; set; }
    public string MigrationHistorySchema { get; set; } = "app";
}

internal class MigrationApplication<TDataContext> : CommandLineApplication
    where TDataContext : DbContext
{
    private MigrationApplication(TDataContext databaseCtx)
    {
        Command("apply-all", config =>
        {
            config.Description = "Do the migration to the latest version.";
            config.OnExecute(() => ApplyAllMigrations(databaseCtx));
        });

        Command("apply", config =>
        {
            config.Description = "Do the migration to the specific migration step.";
            var migrationStep = config.Argument("migration", "name of the migration.");
            config.OnExecute(() => ApplySingleMigration(databaseCtx, migrationStep));
        });

        Command("list-pendings", config =>
        {
            config.Description = "Show the pending migrations.";
            config.OnExecute(() => ListPendings(databaseCtx));
        });

        Command("list-applied", config =>
        {
            config.Description = "Show the migrations applied.";
            config.OnExecute(() => ListApplied(databaseCtx));
        });

        Command("list-all", config =>
        {
            config.Description = "Show the migrations defined applied and not.";
            config.OnExecute(() => ListAll(databaseCtx));
        });

        HelpOption("-? | -h | --help");
    }

    private static IServiceProvider BuildContainer()
    {
        IConfiguration configuration = new ConfigurationBuilder()
            .AddJsonFile("appsettings.json", true, true)
            .AddJsonFile($"appsettings.{Environment.MachineName}.json", true, true)
            .Build();

        var appConfig = configuration.Get<MigrationApplicationConfiguration>();

        IServiceCollection services = new ServiceCollection();

        services.AddDbContext<TDataContext>(optionsBuilder =>
        {
            optionsBuilder.UseSqlServer(configuration.GetConnectionString(appConfig.ConnectionStringName),
                    options =>
                    {
                        options.MigrationsHistoryTable(
                            appConfig.MigrationHistoryTable ?? throw new ConfigurationException($"{nameof(appConfig.MigrationHistoryTable)} must not be null"),
                            appConfig.MigrationHistorySchema);
                        options.MigrationsAssembly(appConfig.MigrationHistoryAssembly ?? Assembly.GetExecutingAssembly().FullName);
                        options.CommandTimeout(30);
                    })
                .UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
        });

        services.AddSingleton(configuration);

        return services.BuildServiceProvider();
    }

    public static MigrationApplication<TDataContext> Build()
    {
        var container = BuildContainer();
        return new MigrationApplication<TDataContext>(container.GetService<TDataContext>());
    }

    private static int ListAll(TDataContext databaseCtx)
    {
        try
        {
            var currentColor = Console.ForegroundColor;
            Console.ForegroundColor = ConsoleColor.Red;
            Console.WriteLine($"List the migration(s) defined to database {databaseCtx.Database.GetDbConnection().Database}.");
            var migrations = databaseCtx.Database.GetMigrations();
            var applieds = migrations as ICollection<string> ?? migrations.ToList();
            Console.ForegroundColor = ConsoleColor.Yellow;
            if (applieds.Any())
            {
                foreach (var applied in applieds)
                {
                    Console.WriteLine($"    - {applied}.");
                }
            }
            else
            {
                Console.WriteLine("No migration has been applied.");
            }

            Console.ForegroundColor = ConsoleColor.Green;
            Console.WriteLine("Finished.");
            Console.ForegroundColor = currentColor;
            return 0;
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
            return -1;
        }
    }

    private static int ListApplied(TDataContext databaseCtx)
    {
        try
        {
            var currentColor = Console.ForegroundColor;
            Console.ForegroundColor = ConsoleColor.Red;
            Console.WriteLine($"List the migration(s) applied to database {databaseCtx.Database.GetDbConnection().Database}.");
            var appliedMigrations = databaseCtx.Database.GetAppliedMigrations();
            var applieds = appliedMigrations as ICollection<string> ?? appliedMigrations.ToList();
            Console.ForegroundColor = ConsoleColor.Yellow;
            if (applieds.Any())
            {
                foreach (var applied in applieds)
                {
                    Console.WriteLine($"    - {applied}.");
                }
            }
            else
            {
                Console.WriteLine("No migration has been applied.");
            }

            Console.ForegroundColor = ConsoleColor.Green;
            Console.WriteLine("Finished.");
            Console.ForegroundColor = currentColor;
            return 0;
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
            return -1;
        }
    }

    private static int ListPendings(TDataContext databaseCtx)
    {
        try
        {
            var currentColor = Console.ForegroundColor;
            Console.ForegroundColor = ConsoleColor.Red;
            Console.WriteLine($"List the pending migration(s) for database {databaseCtx.Database.GetDbConnection().Database}.");
            var pendingMigrations = databaseCtx.Database.GetPendingMigrations();
            var pendings = pendingMigrations as ICollection<string> ?? pendingMigrations.ToList();
            Console.ForegroundColor = ConsoleColor.Yellow;
            if (pendings.Any())
            {
                foreach (var pending in pendings)
                {
                    Console.WriteLine($"    - {pending}.");
                }
            }
            else
            {
                Console.WriteLine("No migration is pending.");
            }

            Console.ForegroundColor = ConsoleColor.Green;
            Console.WriteLine("Finished.");
            Console.ForegroundColor = currentColor;
            return 0;
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
            return -1;
        }
    }

    private static int ApplySingleMigration(TDataContext databaseCtx, CommandArgument? migrationStep)
    {
        try
        {
            var currentColor = Console.ForegroundColor;
            Console.ForegroundColor = ConsoleColor.Red;
            Console.WriteLine($"Migrate the database {databaseCtx.Database.GetDbConnection().Database} up to {migrationStep.Value}.");
            var pendings = databaseCtx.Database.GetPendingMigrations();
            Console.ForegroundColor = ConsoleColor.Yellow;
            if (pendings.Any(p => p.Equals(migrationStep.Value, StringComparison.InvariantCultureIgnoreCase)))
            {
                var migrator = databaseCtx.Database.GetService<IMigrator>();
                migrator.Migrate(migrationStep.Value);
            }
            else
            {
                Console.WriteLine($"No pending migration found with name equal to {migrationStep.Value}.");
            }

            Console.ForegroundColor = ConsoleColor.Green;
            Console.WriteLine("Migration is finished.");
            Console.ForegroundColor = currentColor;
            return 0;
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
            return -1;
        }
    }

    private static int ApplyAllMigrations(TDataContext databaseCtx)
    {
        try
        {
            var currentColor = Console.ForegroundColor;
            Console.ForegroundColor = ConsoleColor.Red;
            Console.WriteLine($"Migrate the database {databaseCtx.Database.GetDbConnection().Database}.");
            var pendings = databaseCtx.Database.GetPendingMigrations();
            Console.ForegroundColor = ConsoleColor.Yellow;
            Console.WriteLine("Pendings migration that will be applied.");

            foreach (var pending in pendings)
            {
                Console.WriteLine($"    - {pending}.");
            }

            databaseCtx.Database.Migrate();
            Console.ForegroundColor = ConsoleColor.Green;
            Console.WriteLine("Migration is finished.");
            Console.ForegroundColor = currentColor;
            return 0;
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
            return -1;
        }
    }
}

Please note the as ICollection<string> ?? .ToList() and the removal of Where().Any(), because Any() is sufficient enough.

Then the project's MigrationTool would look like this (everything else is part of the Arc4u):

public static class Program
{
    public static void Main(string[] args)
    {
        MigrationApplication<DatabaseContext>
            .Build()
            .Execute(args);
    }
}
GFlisch commented 1 year ago

We are indeed preparing the integration for MongoDB and we will follow your idea.