From 13c788d6ec96f78605e224f7efd8b3efe539e53a Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Mon, 3 Mar 2025 11:54:22 +0100 Subject: [PATCH] Add `AsyncMigrationBase`, update base classes and call async methods (#17057) * Add AsyncMigrationBase, update base classes and call async methods * Restored and obsoleted synchronous execution on IMigrationPlanExecutor. * Resolved breaking changes. * Fixed build. * Further obsoletes. * Fix build against v16/dev. * Removed and obsolete code related to post-migrations. * Removed service registration of unused interface. --------- Co-authored-by: Andy Butland --- .../UmbracoBuilder.CoreServices.cs | 3 - .../Install/PackageMigrationRunner.cs | 29 ++-- .../Install/PremigrationUpgrader.cs | 22 +-- .../Install/UnattendedUpgrader.cs | 20 ++- .../Installer/Steps/DatabaseUpgradeStep.cs | 10 +- .../Migrations/AsyncMigrationBase.Database.cs | 147 +++++++++++++++++ .../Migrations/AsyncMigrationBase.cs | 140 ++++++++++++++++ .../Migrations/IMigrationBuilder.cs | 2 +- .../Migrations/IMigrationContext.cs | 7 - .../Migrations/IMigrationPlanExecutor.cs | 28 +++- .../Migrations/Install/DatabaseBuilder.cs | 10 +- .../Migrations/MergeBuilder.cs | 2 +- .../Migrations/MigrationBase.cs | 136 ++-------------- .../Migrations/MigrationBase_Extra.cs | 151 ------------------ .../Migrations/MigrationBuilder.cs | 4 +- .../Migrations/MigrationContext.cs | 12 -- .../Migrations/MigrationPlan.cs | 14 +- .../Migrations/MigrationPlanExecutor.cs | 74 +++------ .../Migrations/NoopMigration.cs | 12 +- .../PostMigrations/CacheRebuilder.cs | 1 + .../PostMigrations/ICacheRebuilder.cs | 1 + .../Migrations/UnscopedAsyncMigrationBase.cs | 34 ++++ .../Migrations/UnscopedMigrationBase.cs | 35 ++-- .../Migrations/Upgrade/Upgrader.cs | 18 +-- .../Packaging/AsyncPackageMigrationBase.cs | 51 ++++++ .../AutomaticPackageMigrationPlan.cs | 18 +-- .../Packaging/IImportPackageBuilder.cs | 2 +- .../Packaging/ImportPackageBuilder.cs | 2 +- .../Packaging/PackageMigrationBase.cs | 46 ++---- .../Umbraco.Core/RuntimeStateTests.cs | 12 +- .../Migrations/AdvancedMigrationTests.cs | 23 +-- .../Migrations/PartialMigrationsTests.cs | 34 ++-- .../Migrations/AlterMigrationTests.cs | 20 ++- .../Migrations/MigrationPlanTests.cs | 10 +- .../Migrations/MigrationTests.cs | 8 +- 35 files changed, 603 insertions(+), 535 deletions(-) create mode 100644 src/Umbraco.Infrastructure/Migrations/AsyncMigrationBase.Database.cs create mode 100644 src/Umbraco.Infrastructure/Migrations/AsyncMigrationBase.cs delete mode 100644 src/Umbraco.Infrastructure/Migrations/MigrationBase_Extra.cs create mode 100644 src/Umbraco.Infrastructure/Migrations/UnscopedAsyncMigrationBase.cs create mode 100644 src/Umbraco.Infrastructure/Packaging/AsyncPackageMigrationBase.cs diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs index 74fa4f374d..6e62e2c965 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs @@ -50,7 +50,6 @@ using Umbraco.Cms.Infrastructure.Mail.Interfaces; using Umbraco.Cms.Infrastructure.Manifest; using Umbraco.Cms.Infrastructure.Migrations; using Umbraco.Cms.Infrastructure.Migrations.Install; -using Umbraco.Cms.Infrastructure.Migrations.PostMigrations; using Umbraco.Cms.Infrastructure.Persistence; using Umbraco.Cms.Infrastructure.Persistence.Mappers; using Umbraco.Cms.Infrastructure.Routing; @@ -148,8 +147,6 @@ public static partial class UmbracoBuilderExtensions builder.Services.AddSingleton(); builder.Services.AddSingleton(factory => new MigrationBuilder(factory)); - builder.Services.AddSingleton(); - builder.Services.AddSingleton(); builder.Services.AddSingleton(); diff --git a/src/Umbraco.Infrastructure/Install/PackageMigrationRunner.cs b/src/Umbraco.Infrastructure/Install/PackageMigrationRunner.cs index 1994363e77..01e2fbe595 100644 --- a/src/Umbraco.Infrastructure/Install/PackageMigrationRunner.cs +++ b/src/Umbraco.Infrastructure/Install/PackageMigrationRunner.cs @@ -50,15 +50,18 @@ public class PackageMigrationRunner _packageMigrationPlans = packageMigrationPlans.ToDictionary(x => x.Name); } + [Obsolete("Please use RunPackageMigrationsIfPendingAsync instead. Scheduled for removal in Umbraco 18.")] + public IEnumerable RunPackageMigrationsIfPending(string packageName) + => RunPackageMigrationsIfPendingAsync(packageName).GetAwaiter().GetResult(); + /// /// Runs all migration plans for a package name if any are pending. /// /// /// - public IEnumerable RunPackageMigrationsIfPending(string packageName) + public async Task> RunPackageMigrationsIfPendingAsync(string packageName) { - IReadOnlyDictionary? keyValues = - _keyValueService.FindByKeyPrefix(Constants.Conventions.Migrations.KeyValuePrefix); + IReadOnlyDictionary? keyValues = _keyValueService.FindByKeyPrefix(Constants.Conventions.Migrations.KeyValuePrefix); IReadOnlyList pendingMigrations = _pendingPackageMigrations.GetPendingPackageMigrations(keyValues); IEnumerable packagePlans = _packageMigrationPlans.Values @@ -66,31 +69,35 @@ public class PackageMigrationRunner .Where(x => pendingMigrations.Contains(x.Name)) .Select(x => x.Name); - return RunPackagePlans(packagePlans); + return await RunPackagePlansAsync(packagePlans).ConfigureAwait(false); } /// /// Checks if all executed package migrations succeeded for a package. /// - public Task> RunPendingPackageMigrations(string packageName) + public async Task> RunPendingPackageMigrations(string packageName) { // Check if there are any migrations if (_packageMigrationPlans.ContainsKey(packageName) == false) { - return Task.FromResult(Attempt.FailWithStatus(PackageMigrationOperationStatus.NotFound, false)); + return Attempt.FailWithStatus(PackageMigrationOperationStatus.NotFound, false); } // Run the migrations - IEnumerable executedMigrationPlans = RunPackageMigrationsIfPending(packageName); + IEnumerable executedMigrationPlans = await RunPackageMigrationsIfPendingAsync(packageName).ConfigureAwait(false); if (executedMigrationPlans.Any(plan => plan.Successful == false)) { - return Task.FromResult(Attempt.FailWithStatus(PackageMigrationOperationStatus.CancelledByFailedMigration, false)); + return Attempt.FailWithStatus(PackageMigrationOperationStatus.CancelledByFailedMigration, false); } - return Task.FromResult(Attempt.SucceedWithStatus(PackageMigrationOperationStatus.Success, true)); + return Attempt.SucceedWithStatus(PackageMigrationOperationStatus.Success, true); } + [Obsolete("Please use RunPackageMigrationsIfPendingAsync instead. Scheduled for removal in Umbraco 18.")] + public IEnumerable RunPackagePlans(IEnumerable plansToRun) + => RunPackagePlansAsync(plansToRun).GetAwaiter().GetResult(); + /// /// Runs the all specified package migration plans and publishes a /// if all are successful. @@ -98,7 +105,7 @@ public class PackageMigrationRunner /// /// /// If any plan fails it will throw an exception. - public IEnumerable RunPackagePlans(IEnumerable plansToRun) + public async Task> RunPackagePlansAsync(IEnumerable plansToRun) { List results = new(); @@ -120,7 +127,7 @@ public class PackageMigrationRunner Upgrader upgrader = new(plan); // This may throw, if so the transaction will be rolled back - results.Add(upgrader.Execute(_migrationPlanExecutor, _scopeProvider, _keyValueService)); + results.Add(await upgrader.ExecuteAsync(_migrationPlanExecutor, _scopeProvider, _keyValueService).ConfigureAwait(false)); } } diff --git a/src/Umbraco.Infrastructure/Install/PremigrationUpgrader.cs b/src/Umbraco.Infrastructure/Install/PremigrationUpgrader.cs index 1be5cb1ee8..20142fc19c 100644 --- a/src/Umbraco.Infrastructure/Install/PremigrationUpgrader.cs +++ b/src/Umbraco.Infrastructure/Install/PremigrationUpgrader.cs @@ -36,42 +36,36 @@ public class PremigrationUpgrader : INotificationAsyncHandler( - "Starting premigration upgrade.", - "Unattended premigration completed.")) + using (_profilingLogger.IsEnabled(LogLevel.Verbose) is false ? null : _profilingLogger.TraceDuration("Starting premigration upgrade.", "Unattended premigration completed.")) { - DatabaseBuilder.Result? result = _databaseBuilder.UpgradeSchemaAndData(plan); + DatabaseBuilder.Result? result = await _databaseBuilder.UpgradeSchemaAndDataAsync(plan).ConfigureAwait(false); if (result?.Success is false) { - var innerException = new IOException( - "An error occurred while running the premigration upgrade.\n" + result.Message); + var innerException = new IOException("An error occurred while running the premigration upgrade.\n" + result.Message); _runtimeState.Configure(RuntimeLevel.BootFailed, RuntimeLevelReason.BootFailedOnException, innerException); } - notification.UpgradeResult = - RuntimePremigrationsUpgradeNotification.PremigrationUpgradeResult.CoreUpgradeComplete; + notification.UpgradeResult = RuntimePremigrationsUpgradeNotification.PremigrationUpgradeResult.CoreUpgradeComplete; } - - return Task.CompletedTask; } private bool HasMissingPremigrations(UmbracoPremigrationPlan umbracoPremigrationPlan) diff --git a/src/Umbraco.Infrastructure/Install/UnattendedUpgrader.cs b/src/Umbraco.Infrastructure/Install/UnattendedUpgrader.cs index 9bf5c91eb8..65d841917d 100644 --- a/src/Umbraco.Infrastructure/Install/UnattendedUpgrader.cs +++ b/src/Umbraco.Infrastructure/Install/UnattendedUpgrader.cs @@ -62,7 +62,7 @@ public class UnattendedUpgrader : INotificationAsyncHandler( "Starting unattended upgrade.", "Unattended upgrade completed.")) { - DatabaseBuilder.Result? result = _databaseBuilder.UpgradeSchemaAndData(plan); + DatabaseBuilder.Result? result = await _databaseBuilder.UpgradeSchemaAndDataAsync(plan); if (result?.Success == false) { var innerException = new UnattendedInstallException( diff --git a/src/Umbraco.Infrastructure/Installer/Steps/DatabaseUpgradeStep.cs b/src/Umbraco.Infrastructure/Installer/Steps/DatabaseUpgradeStep.cs index 4d1e99a27e..7bebc3d4fa 100644 --- a/src/Umbraco.Infrastructure/Installer/Steps/DatabaseUpgradeStep.cs +++ b/src/Umbraco.Infrastructure/Installer/Steps/DatabaseUpgradeStep.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration; using Umbraco.Cms.Core.Installer; @@ -35,21 +35,21 @@ public class DatabaseUpgradeStep : StepBase, IInstallStep, IUpgradeStep public Task> ExecuteAsync() => ExecuteInternalAsync(); - private Task> ExecuteInternalAsync() + private async Task> ExecuteInternalAsync() { _logger.LogInformation("Running 'Upgrade' service"); var plan = new UmbracoPlan(_umbracoVersion); // TODO: Clear CSRF cookies with notification. - DatabaseBuilder.Result? result = _databaseBuilder.UpgradeSchemaAndData(plan); + DatabaseBuilder.Result? result = await _databaseBuilder.UpgradeSchemaAndDataAsync(plan).ConfigureAwait(false); if (result?.Success == false) { - return Task.FromResult(FailWithMessage("The database failed to upgrade. ERROR: " + result.Message)); + return FailWithMessage("The database failed to upgrade. ERROR: " + result.Message); } - return Task.FromResult(Success()); + return Success(); } public Task RequiresExecutionAsync(InstallData model) => ShouldExecute(); diff --git a/src/Umbraco.Infrastructure/Migrations/AsyncMigrationBase.Database.cs b/src/Umbraco.Infrastructure/Migrations/AsyncMigrationBase.Database.cs new file mode 100644 index 0000000000..21ea923779 --- /dev/null +++ b/src/Umbraco.Infrastructure/Migrations/AsyncMigrationBase.Database.cs @@ -0,0 +1,147 @@ +using Umbraco.Cms.Infrastructure.Migrations.Expressions.Execute.Expressions; +using Umbraco.Cms.Infrastructure.Persistence.DatabaseModelDefinitions; +using Umbraco.Cms.Infrastructure.Persistence.SqlSyntax; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Infrastructure.Migrations; + +/// +/// Provides a base class to all migrations. +/// +public abstract partial class AsyncMigrationBase +{ + // provides extra methods for migrations + protected void AddColumn(string columnName) + { + TableDefinition? table = DefinitionFactory.GetTableDefinition(typeof(T), SqlSyntax); + AddColumn(table, table.Name!, columnName); + } + + protected void AddColumnIfNotExists(IEnumerable columns, string columnName) + { + TableDefinition? table = DefinitionFactory.GetTableDefinition(typeof(T), SqlSyntax); + if (columns.Any(x => x.TableName.InvariantEquals(table.Name) && !x.ColumnName.InvariantEquals(columnName))) + { + AddColumn(table, table.Name!, columnName); + } + } + + protected void AddColumn(string tableName, string columnName) + { + TableDefinition? table = DefinitionFactory.GetTableDefinition(typeof(T), SqlSyntax); + AddColumn(table, tableName, columnName); + } + + protected void AddColumnIfNotExists(IEnumerable columns, string tableName, string columnName) + { + TableDefinition? table = DefinitionFactory.GetTableDefinition(typeof(T), SqlSyntax); + if (columns.Any(x => x.TableName.InvariantEquals(tableName) && !x.ColumnName.InvariantEquals(columnName))) + { + AddColumn(table, tableName, columnName); + } + } + + protected void AddColumn(string columnName, out IEnumerable sqls) + { + TableDefinition? table = DefinitionFactory.GetTableDefinition(typeof(T), SqlSyntax); + AddColumn(table, table.Name!, columnName, out sqls); + } + + private void AddColumn(TableDefinition table, string tableName, string columnName) + { + if (ColumnExists(tableName, columnName)) + { + return; + } + + ColumnDefinition? column = table.Columns.First(x => x.Name == columnName); + var createSql = SqlSyntax.Format(column); + + Execute.Sql(string.Format(SqlSyntax.AddColumn, SqlSyntax.GetQuotedTableName(tableName), createSql)).Do(); + } + + protected void AddColumn(string tableName, string columnName, out IEnumerable sqls) + { + TableDefinition? table = DefinitionFactory.GetTableDefinition(typeof(T), SqlSyntax); + AddColumn(table, tableName, columnName, out sqls); + } + + protected void AlterColumn(string tableName, string columnName) + { + TableDefinition? table = DefinitionFactory.GetTableDefinition(typeof(T), SqlSyntax); + ColumnDefinition? column = table.Columns.First(x => x.Name == columnName); + SqlSyntax.Format(column, SqlSyntax.GetQuotedTableName(tableName), out IEnumerable? sqls); + foreach (var sql in sqls) + { + Execute.Sql(sql).Do(); + } + } + + private void AddColumn(TableDefinition table, string tableName, string columnName, out IEnumerable sqls) + { + if (ColumnExists(tableName, columnName)) + { + sqls = Enumerable.Empty(); + return; + } + + ColumnDefinition? column = table.Columns.First(x => x.Name == columnName); + var createSql = SqlSyntax.Format(column, SqlSyntax.GetQuotedTableName(tableName), out sqls); + Execute.Sql(string.Format(SqlSyntax.AddColumn, SqlSyntax.GetQuotedTableName(tableName), createSql)).Do(); + } + + protected void ReplaceColumn(string tableName, string currentName, string newName) + { + Execute.Sql(SqlSyntax.FormatColumnRename(tableName, currentName, newName)).Do(); + AlterColumn(tableName, newName); + } + + protected bool TableExists(string tableName) + { + IEnumerable? tables = SqlSyntax.GetTablesInSchema(Context.Database); + return tables.Any(x => x.InvariantEquals(tableName)); + } + + protected bool IndexExists(string indexName) + { + IEnumerable>? indexes = SqlSyntax.GetDefinedIndexes(Context.Database); + return indexes.Any(x => x.Item2.InvariantEquals(indexName)); + } + + protected void CreateIndex(string toCreate) + { + TableDefinition tableDef = DefinitionFactory.GetTableDefinition(typeof(T), Context.SqlContext.SqlSyntax); + IndexDefinition index = tableDef.Indexes.First(x => x.Name == toCreate); + new ExecuteSqlStatementExpression(Context) { SqlStatement = Context.SqlContext.SqlSyntax.Format(index) } + .Execute(); + } + + protected void DeleteIndex(string toDelete) + { + if (!IndexExists(toDelete)) + { + return; + } + + TableDefinition tableDef = DefinitionFactory.GetTableDefinition(typeof(T), Context.SqlContext.SqlSyntax); + Delete.Index(toDelete).OnTable(tableDef.Name).Do(); + } + + protected bool PrimaryKeyExists(string tableName, string primaryKeyName) + { + return SqlSyntax.DoesPrimaryKeyExist(Context.Database, tableName, primaryKeyName); + } + + protected bool ColumnExists(string tableName, string columnName) + { + ColumnInfo[]? columns = SqlSyntax.GetColumnsInSchema(Context.Database).Distinct().ToArray(); + return columns.Any(x => x.TableName.InvariantEquals(tableName) && x.ColumnName.InvariantEquals(columnName)); + } + + protected string? ColumnType(string tableName, string columnName) + { + ColumnInfo[]? columns = SqlSyntax.GetColumnsInSchema(Context.Database).Distinct().ToArray(); + ColumnInfo? column = columns.FirstOrDefault(x => x.TableName.InvariantEquals(tableName) && x.ColumnName.InvariantEquals(columnName)); + return column?.DataType; + } +} diff --git a/src/Umbraco.Infrastructure/Migrations/AsyncMigrationBase.cs b/src/Umbraco.Infrastructure/Migrations/AsyncMigrationBase.cs new file mode 100644 index 0000000000..461a79a0b4 --- /dev/null +++ b/src/Umbraco.Infrastructure/Migrations/AsyncMigrationBase.cs @@ -0,0 +1,140 @@ +using Microsoft.Extensions.Logging; +using NPoco; +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Infrastructure.Migrations.Expressions.Alter; +using Umbraco.Cms.Infrastructure.Migrations.Expressions.Create; +using Umbraco.Cms.Infrastructure.Migrations.Expressions.Delete; +using Umbraco.Cms.Infrastructure.Migrations.Expressions.Execute; +using Umbraco.Cms.Infrastructure.Migrations.Expressions.Insert; +using Umbraco.Cms.Infrastructure.Migrations.Expressions.Rename; +using Umbraco.Cms.Infrastructure.Migrations.Expressions.Update; +using Umbraco.Cms.Infrastructure.Persistence; +using Umbraco.Cms.Infrastructure.Persistence.SqlSyntax; + +namespace Umbraco.Cms.Infrastructure.Migrations; + +/// +/// Provides a base class to all migrations. +/// +public abstract partial class AsyncMigrationBase : IDiscoverable +{ + /// + /// Initializes a new instance of the class. + /// + /// A migration context. + protected AsyncMigrationBase(IMigrationContext context) + => Context = context; + + /// + /// Builds an Alter expression. + /// + public IAlterBuilder Alter => BeginBuild(new AlterBuilder(Context)); + + /// + /// Gets the migration context. + /// + protected IMigrationContext Context { get; } + + /// + /// Gets the logger. + /// + protected ILogger Logger => Context.Logger; + + /// + /// Gets the SQL syntax. + /// + protected ISqlSyntaxProvider SqlSyntax => Context.SqlContext.SqlSyntax; + + /// + /// Gets the database instance. + /// + protected IUmbracoDatabase Database => Context.Database; + + /// + /// Gets the database type. + /// + protected DatabaseType DatabaseType => Context.Database.DatabaseType; + + /// + /// Builds a Create expression. + /// + public ICreateBuilder Create => BeginBuild(new CreateBuilder(Context)); + + /// + /// Builds a Delete expression. + /// + public IDeleteBuilder Delete => BeginBuild(new DeleteBuilder(Context)); + + /// + /// Builds an Execute expression. + /// + public IExecuteBuilder Execute => BeginBuild(new ExecuteBuilder(Context)); + + /// + /// Builds an Insert expression. + /// + public IInsertBuilder Insert => BeginBuild(new InsertBuilder(Context)); + + /// + /// Builds a Rename expression. + /// + public IRenameBuilder Rename => BeginBuild(new RenameBuilder(Context)); + + /// + /// Builds an Update expression. + /// + public IUpdateBuilder Update => BeginBuild(new UpdateBuilder(Context)); + + /// + /// If this is set to true, the published cache will be rebuild upon successful completion of the migration. + /// + public bool RebuildCache { get; set; } + + /// + /// If this is set to true, all back-office client tokens will be revoked upon successful completion of the migration. + /// + public bool InvalidateBackofficeUserAccess { get; set; } + + /// + /// Runs the migration. + /// + public async Task RunAsync() + { + await MigrateAsync().ConfigureAwait(false); + + // ensure there is no building expression + // ie we did not forget to .Do() an expression + if (Context.BuildingExpression) + { + throw new IncompleteMigrationExpressionException("The migration has run, but leaves an expression that has not run."); + } + } + + /// + /// Creates a new Sql statement. + /// + protected Sql Sql() => Context.SqlContext.Sql(); + + /// + /// Creates a new Sql statement with arguments. + /// + protected Sql Sql(string sql, params object[] args) => Context.SqlContext.Sql(sql, args); + + /// + /// Executes the migration. + /// + protected abstract Task MigrateAsync(); + + // ensures we are not already building, + // ie we did not forget to .Do() an expression + private protected T BeginBuild(T builder) + { + if (Context.BuildingExpression) + { + throw new IncompleteMigrationExpressionException("Cannot create a new expression: the previous expression has not run."); + } + + Context.BuildingExpression = true; + return builder; + } +} diff --git a/src/Umbraco.Infrastructure/Migrations/IMigrationBuilder.cs b/src/Umbraco.Infrastructure/Migrations/IMigrationBuilder.cs index 078bfcaf38..8379404c05 100644 --- a/src/Umbraco.Infrastructure/Migrations/IMigrationBuilder.cs +++ b/src/Umbraco.Infrastructure/Migrations/IMigrationBuilder.cs @@ -2,5 +2,5 @@ namespace Umbraco.Cms.Infrastructure.Migrations; public interface IMigrationBuilder { - MigrationBase Build(Type migrationType, IMigrationContext context); + AsyncMigrationBase Build(Type migrationType, IMigrationContext context); } diff --git a/src/Umbraco.Infrastructure/Migrations/IMigrationContext.cs b/src/Umbraco.Infrastructure/Migrations/IMigrationContext.cs index cf68617315..932dbd4628 100644 --- a/src/Umbraco.Infrastructure/Migrations/IMigrationContext.cs +++ b/src/Umbraco.Infrastructure/Migrations/IMigrationContext.cs @@ -38,13 +38,6 @@ public interface IMigrationContext /// bool BuildingExpression { get; set; } - /// - /// Adds a post-migration. - /// - [Obsolete("This will be removed in the V13, and replaced with a RebuildCache flag on the MigrationBase")] - void AddPostMigration() - where TMigration : MigrationBase; - bool IsCompleted { get; } void Complete(); diff --git a/src/Umbraco.Infrastructure/Migrations/IMigrationPlanExecutor.cs b/src/Umbraco.Infrastructure/Migrations/IMigrationPlanExecutor.cs index ee3f787c12..70350f5e93 100644 --- a/src/Umbraco.Infrastructure/Migrations/IMigrationPlanExecutor.cs +++ b/src/Umbraco.Infrastructure/Migrations/IMigrationPlanExecutor.cs @@ -5,9 +5,20 @@ namespace Umbraco.Cms.Core.Migrations; public interface IMigrationPlanExecutor { - [Obsolete("Use ExecutePlan instead.")] + [Obsolete("Use ExecutePlan instead. Scheduled for removal in Umbraco 17.")] string Execute(MigrationPlan plan, string fromState); + /// + /// Executes the migration plan. + /// + /// The migration plan to execute. + /// The state to start execution at. + /// containing information about the plan execution, such as completion state and the steps that ran. + /// + /// Each migration in the plan, may or may not run in a scope depending on the type of plan. + /// A plan can complete partially, the changes of each completed migration will be saved. + /// + [Obsolete("Use ExecutePlanAsync instead. Scheduled for removal in Umbraco 18.")] ExecutedMigrationPlan ExecutePlan(MigrationPlan plan, string fromState) { var state = Execute(plan, fromState); @@ -15,4 +26,19 @@ public interface IMigrationPlanExecutor // We have no real way of knowing whether it was successfull or not here, assume true. return new ExecutedMigrationPlan(plan, fromState, state, true, plan.Transitions.Select(x => x.Value).WhereNotNull().ToList()); } + + /// + /// Executes the migration plan asynchronously. + /// + /// The migration plan to execute. + /// The state to start execution at. + /// A Task of containing information about the plan execution, such as completion state and the steps that ran. + /// + /// Each migration in the plan, may or may not run in a scope depending on the type of plan. + /// A plan can complete partially, the changes of each completed migration will be saved. + /// + Task ExecutePlanAsync(MigrationPlan plan, string fromState) +#pragma warning disable CS0618 // Type or member is obsolete + => Task.FromResult(ExecutePlan(plan, fromState)); +#pragma warning restore CS0618 // Type or member is obsolete } diff --git a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseBuilder.cs b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseBuilder.cs index fffa0ced9f..4915e68efa 100644 --- a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseBuilder.cs +++ b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseBuilder.cs @@ -353,8 +353,14 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install } } + [Obsolete("Use UpgradeSchemaAndDataAsync instead. Scheduled for removal in Umbraco 18.")] public Result? UpgradeSchemaAndData(UmbracoPlan plan) => UpgradeSchemaAndData((MigrationPlan)plan); + [Obsolete("Use UpgradeSchemaAndDataAsync instead. Scheduled for removal in Umbraco 18.")] + public Result? UpgradeSchemaAndData(MigrationPlan plan) => UpgradeSchemaAndDataAsync(plan).GetAwaiter().GetResult(); + + public async Task UpgradeSchemaAndDataAsync(UmbracoPlan plan) => await UpgradeSchemaAndDataAsync((MigrationPlan)plan).ConfigureAwait(false); + /// /// Upgrades the database schema and data by running migrations. /// @@ -363,7 +369,7 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install /// configured and it is possible to connect to the database. /// Runs whichever migrations need to run. /// - public Result? UpgradeSchemaAndData(MigrationPlan plan) + public async Task UpgradeSchemaAndDataAsync(MigrationPlan plan) { try { @@ -377,7 +383,7 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install // upgrade var upgrader = new Upgrader(plan); - ExecutedMigrationPlan result = upgrader.Execute(_migrationPlanExecutor, _scopeProvider, _keyValueService); + ExecutedMigrationPlan result = await upgrader.ExecuteAsync(_migrationPlanExecutor, _scopeProvider, _keyValueService).ConfigureAwait(false); _aggregator.Publish(new UmbracoPlanExecutedNotification { ExecutedPlan = result }); diff --git a/src/Umbraco.Infrastructure/Migrations/MergeBuilder.cs b/src/Umbraco.Infrastructure/Migrations/MergeBuilder.cs index 3e80d9ebc5..9e382022d2 100644 --- a/src/Umbraco.Infrastructure/Migrations/MergeBuilder.cs +++ b/src/Umbraco.Infrastructure/Migrations/MergeBuilder.cs @@ -25,7 +25,7 @@ public class MergeBuilder /// Adds a transition to a target state through a migration. /// public MergeBuilder To(string targetState) - where TMigration : MigrationBase + where TMigration : AsyncMigrationBase => To(targetState, typeof(TMigration)); /// diff --git a/src/Umbraco.Infrastructure/Migrations/MigrationBase.cs b/src/Umbraco.Infrastructure/Migrations/MigrationBase.cs index cbdccc1ca4..9e539a06a9 100644 --- a/src/Umbraco.Infrastructure/Migrations/MigrationBase.cs +++ b/src/Umbraco.Infrastructure/Migrations/MigrationBase.cs @@ -1,143 +1,27 @@ -using Microsoft.Extensions.Logging; -using NPoco; -using Umbraco.Cms.Core.Composing; -using Umbraco.Cms.Infrastructure.Migrations.Expressions.Alter; -using Umbraco.Cms.Infrastructure.Migrations.Expressions.Create; -using Umbraco.Cms.Infrastructure.Migrations.Expressions.Delete; -using Umbraco.Cms.Infrastructure.Migrations.Expressions.Execute; -using Umbraco.Cms.Infrastructure.Migrations.Expressions.Insert; -using Umbraco.Cms.Infrastructure.Migrations.Expressions.Rename; -using Umbraco.Cms.Infrastructure.Migrations.Expressions.Update; -using Umbraco.Cms.Infrastructure.Persistence; -using Umbraco.Cms.Infrastructure.Persistence.SqlSyntax; -using Umbraco.Cms.Infrastructure.Scoping; - namespace Umbraco.Cms.Infrastructure.Migrations; -/// -/// Provides a base class to all migrations. -/// -public abstract partial class MigrationBase : IDiscoverable +/// +[Obsolete("Use AsyncMigrationBase instead. This class will be removed in a future version.")] +public abstract class MigrationBase : AsyncMigrationBase { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// A migration context. protected MigrationBase(IMigrationContext context) - => Context = context; + : base(context) + { } - /// - /// Builds an Alter expression. - /// - public IAlterBuilder Alter => BeginBuild(new AlterBuilder(Context)); - - /// - /// Gets the migration context. - /// - protected IMigrationContext Context { get; } - - /// - /// Gets the logger. - /// - protected ILogger Logger => Context.Logger; - - /// - /// Gets the Sql syntax. - /// - protected ISqlSyntaxProvider SqlSyntax => Context.SqlContext.SqlSyntax; - - /// - /// Gets the database instance. - /// - protected IUmbracoDatabase Database => Context.Database; - - /// - /// Gets the database type. - /// - protected DatabaseType DatabaseType => Context.Database.DatabaseType; - - /// - /// Builds a Create expression. - /// - public ICreateBuilder Create => BeginBuild(new CreateBuilder(Context)); - - /// - /// Builds a Delete expression. - /// - public IDeleteBuilder Delete => BeginBuild(new DeleteBuilder(Context)); - - /// - /// Builds an Execute expression. - /// - public IExecuteBuilder Execute => BeginBuild(new ExecuteBuilder(Context)); - - /// - /// Builds an Insert expression. - /// - public IInsertBuilder Insert => BeginBuild(new InsertBuilder(Context)); - - /// - /// Builds a Rename expression. - /// - public IRenameBuilder Rename => BeginBuild(new RenameBuilder(Context)); - - /// - /// Builds an Update expression. - /// - public IUpdateBuilder Update => BeginBuild(new UpdateBuilder(Context)); - - /// - /// If this is set to true, the published cache will be rebuild upon successful completion of the migration. - /// - public bool RebuildCache { get; set; } - - /// - /// If this is set to true, all backoffice client tokens will be revoked upon successful completion of the migration. - /// - public bool InvalidateBackofficeUserAccess { get; set; } - - /// - /// Runs the migration. - /// - public void Run() + /// + protected override Task MigrateAsync() { Migrate(); - // ensure there is no building expression - // ie we did not forget to .Do() an expression - if (Context.BuildingExpression) - { - throw new IncompleteMigrationExpressionException( - "The migration has run, but leaves an expression that has not run."); - } + return Task.CompletedTask; } /// - /// Creates a new Sql statement. - /// - protected Sql Sql() => Context.SqlContext.Sql(); - - /// - /// Creates a new Sql statement with arguments. - /// - protected Sql Sql(string sql, params object[] args) => Context.SqlContext.Sql(sql, args); - - /// - /// Executes the migration. + /// Executes the migration. /// protected abstract void Migrate(); - - // ensures we are not already building, - // ie we did not forget to .Do() an expression - private protected T BeginBuild(T builder) - { - if (Context.BuildingExpression) - { - throw new IncompleteMigrationExpressionException( - "Cannot create a new expression: the previous expression has not run."); - } - - Context.BuildingExpression = true; - return builder; - } } diff --git a/src/Umbraco.Infrastructure/Migrations/MigrationBase_Extra.cs b/src/Umbraco.Infrastructure/Migrations/MigrationBase_Extra.cs deleted file mode 100644 index 23de94e824..0000000000 --- a/src/Umbraco.Infrastructure/Migrations/MigrationBase_Extra.cs +++ /dev/null @@ -1,151 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Umbraco.Cms.Infrastructure.Persistence; -using Umbraco.Cms.Infrastructure.Persistence.DatabaseModelDefinitions; -using Umbraco.Cms.Infrastructure.Migrations.Expressions.Execute.Expressions; -using Umbraco.Cms.Infrastructure.Persistence.SqlSyntax; -using Umbraco.Extensions; - -namespace Umbraco.Cms.Infrastructure.Migrations -{ - /// - /// Provides a base class to all migrations. - /// - public abstract partial class MigrationBase - { - // provides extra methods for migrations - protected void AddColumn(string columnName) - { - TableDefinition? table = DefinitionFactory.GetTableDefinition(typeof(T), SqlSyntax); - AddColumn(table, table.Name!, columnName); - } - - protected void AddColumnIfNotExists(IEnumerable columns, string columnName) - { - TableDefinition? table = DefinitionFactory.GetTableDefinition(typeof(T), SqlSyntax); - if (columns.Any(x => x.TableName.InvariantEquals(table.Name) && !x.ColumnName.InvariantEquals(columnName))) - { - AddColumn(table, table.Name!, columnName); - } - } - - protected void AddColumn(string tableName, string columnName) - { - TableDefinition? table = DefinitionFactory.GetTableDefinition(typeof(T), SqlSyntax); - AddColumn(table, tableName, columnName); - } - - protected void AddColumnIfNotExists(IEnumerable columns, string tableName, string columnName) - { - TableDefinition? table = DefinitionFactory.GetTableDefinition(typeof(T), SqlSyntax); - if (columns.Any(x => x.TableName.InvariantEquals(tableName) && !x.ColumnName.InvariantEquals(columnName))) - { - AddColumn(table, tableName, columnName); - } - } - - protected void AddColumn(string columnName, out IEnumerable sqls) - { - TableDefinition? table = DefinitionFactory.GetTableDefinition(typeof(T), SqlSyntax); - AddColumn(table, table.Name!, columnName, out sqls); - } - - private void AddColumn(TableDefinition table, string tableName, string columnName) - { - if (ColumnExists(tableName, columnName)) - { - return; - } - - ColumnDefinition? column = table.Columns.First(x => x.Name == columnName); - var createSql = SqlSyntax.Format(column); - - Execute.Sql(string.Format(SqlSyntax.AddColumn, SqlSyntax.GetQuotedTableName(tableName), createSql)).Do(); - } - - protected void AddColumn(string tableName, string columnName, out IEnumerable sqls) - { - TableDefinition? table = DefinitionFactory.GetTableDefinition(typeof(T), SqlSyntax); - AddColumn(table, tableName, columnName, out sqls); - } - - protected void AlterColumn(string tableName, string columnName) - { - TableDefinition? table = DefinitionFactory.GetTableDefinition(typeof(T), SqlSyntax); - ColumnDefinition? column = table.Columns.First(x => x.Name == columnName); - SqlSyntax.Format(column, SqlSyntax.GetQuotedTableName(tableName), out IEnumerable? sqls); - foreach (var sql in sqls) - { - Execute.Sql(sql).Do(); - } - } - - private void AddColumn(TableDefinition table, string tableName, string columnName, out IEnumerable sqls) - { - if (ColumnExists(tableName, columnName)) - { - sqls = Enumerable.Empty(); - return; - } - - ColumnDefinition? column = table.Columns.First(x => x.Name == columnName); - var createSql = SqlSyntax.Format(column, SqlSyntax.GetQuotedTableName(tableName), out sqls); - Execute.Sql(string.Format(SqlSyntax.AddColumn, SqlSyntax.GetQuotedTableName(tableName), createSql)).Do(); - } - - protected void ReplaceColumn(string tableName, string currentName, string newName) - { - Execute.Sql(SqlSyntax.FormatColumnRename(tableName, currentName, newName)).Do(); - AlterColumn(tableName, newName); - } - - protected bool TableExists(string tableName) - { - IEnumerable? tables = SqlSyntax.GetTablesInSchema(Context.Database); - return tables.Any(x => x.InvariantEquals(tableName)); - } - - protected bool IndexExists(string indexName) - { - IEnumerable>? indexes = SqlSyntax.GetDefinedIndexes(Context.Database); - return indexes.Any(x => x.Item2.InvariantEquals(indexName)); - } - - protected void CreateIndex(string toCreate) - { - TableDefinition tableDef = DefinitionFactory.GetTableDefinition(typeof(T), Context.SqlContext.SqlSyntax); - IndexDefinition index = tableDef.Indexes.First(x => x.Name == toCreate); - new ExecuteSqlStatementExpression(Context) { SqlStatement = Context.SqlContext.SqlSyntax.Format(index) } - .Execute(); - } - - protected void DeleteIndex(string toDelete) - { - if (!IndexExists(toDelete)) - { - return; - } - - TableDefinition tableDef = DefinitionFactory.GetTableDefinition(typeof(T), Context.SqlContext.SqlSyntax); - Delete.Index(toDelete).OnTable(tableDef.Name).Do(); - } - - protected bool PrimaryKeyExists(string tableName, string primaryKeyName) - { - return SqlSyntax.DoesPrimaryKeyExist(Context.Database, tableName, primaryKeyName); - } - - protected bool ColumnExists(string tableName, string columnName) - { - ColumnInfo[]? columns = SqlSyntax.GetColumnsInSchema(Context.Database).Distinct().ToArray(); - return columns.Any(x => x.TableName.InvariantEquals(tableName) && x.ColumnName.InvariantEquals(columnName)); - } - - protected string? ColumnType(string tableName, string columnName) - { - ColumnInfo[]? columns = SqlSyntax.GetColumnsInSchema(Context.Database).Distinct().ToArray(); - ColumnInfo? column = columns.FirstOrDefault(x => x.TableName.InvariantEquals(tableName) && x.ColumnName.InvariantEquals(columnName)); - return column?.DataType; - } - } -} diff --git a/src/Umbraco.Infrastructure/Migrations/MigrationBuilder.cs b/src/Umbraco.Infrastructure/Migrations/MigrationBuilder.cs index 40db38e053..6edeeddeed 100644 --- a/src/Umbraco.Infrastructure/Migrations/MigrationBuilder.cs +++ b/src/Umbraco.Infrastructure/Migrations/MigrationBuilder.cs @@ -8,6 +8,6 @@ public class MigrationBuilder : IMigrationBuilder public MigrationBuilder(IServiceProvider container) => _container = container; - public MigrationBase Build(Type migrationType, IMigrationContext context) => - (MigrationBase)_container.CreateInstance(migrationType, context); + public AsyncMigrationBase Build(Type migrationType, IMigrationContext context) => + (AsyncMigrationBase)_container.CreateInstance(migrationType, context); } diff --git a/src/Umbraco.Infrastructure/Migrations/MigrationContext.cs b/src/Umbraco.Infrastructure/Migrations/MigrationContext.cs index 4af60ece42..173ee942c4 100644 --- a/src/Umbraco.Infrastructure/Migrations/MigrationContext.cs +++ b/src/Umbraco.Infrastructure/Migrations/MigrationContext.cs @@ -24,10 +24,6 @@ internal class MigrationContext : IMigrationContext Logger = logger ?? throw new ArgumentNullException(nameof(logger)); } - // this is only internally exposed - [Obsolete("This will be removed in the V13, and replaced with a RebuildCache flag on the MigrationBase")] - internal IReadOnlyList PostMigrations => _postMigrations; - /// public ILogger Logger { get; } @@ -58,12 +54,4 @@ internal class MigrationContext : IMigrationContext IsCompleted = true; } - - /// - [Obsolete("This will be removed in the V13, and replaced with a RebuildCache flag on the MigrationBase, and a UmbracoPlanExecutedNotification.")] - public void AddPostMigration() - where TMigration : MigrationBase => - - // just adding - will be de-duplicated when executing - _postMigrations.Add(typeof(TMigration)); } diff --git a/src/Umbraco.Infrastructure/Migrations/MigrationPlan.cs b/src/Umbraco.Infrastructure/Migrations/MigrationPlan.cs index 3be0a01a4f..df8e85a562 100644 --- a/src/Umbraco.Infrastructure/Migrations/MigrationPlan.cs +++ b/src/Umbraco.Infrastructure/Migrations/MigrationPlan.cs @@ -115,9 +115,9 @@ public class MigrationPlan throw new ArgumentNullException(nameof(migration)); } - if (!migration.Implements()) + if (!migration.Implements()) { - throw new ArgumentException($"Type {migration.Name} does not implement IMigration.", nameof(migration)); + throw new ArgumentException($"Type {migration.Name} does not implement AsyncMigrationBase.", nameof(migration)); } sourceState = sourceState.Trim(); @@ -155,11 +155,11 @@ public class MigrationPlan /// Adds a transition to a target state through a migration. /// public MigrationPlan To(string targetState) - where TMigration : MigrationBase + where TMigration : AsyncMigrationBase => To(targetState, typeof(TMigration)); public MigrationPlan To(Guid targetState) - where TMigration : MigrationBase + where TMigration : AsyncMigrationBase => To(targetState, typeof(TMigration)); /// @@ -191,8 +191,8 @@ public class MigrationPlan /// /// The new target state. public MigrationPlan ToWithReplace(string recoverState, string targetState) - where TMigrationNew : MigrationBase - where TMigrationRecover : MigrationBase + where TMigrationNew : AsyncMigrationBase + where TMigrationRecover : AsyncMigrationBase { To(targetState); From(recoverState).To(targetState); @@ -206,7 +206,7 @@ public class MigrationPlan /// The previous target state, which we can recover from directly. /// The new target state. public MigrationPlan ToWithReplace(string recoverState, string targetState) - where TMigrationNew : MigrationBase + where TMigrationNew : AsyncMigrationBase { To(targetState); From(recoverState).To(targetState); diff --git a/src/Umbraco.Infrastructure/Migrations/MigrationPlanExecutor.cs b/src/Umbraco.Infrastructure/Migrations/MigrationPlanExecutor.cs index bd9b4d7f05..a0c42243e3 100644 --- a/src/Umbraco.Infrastructure/Migrations/MigrationPlanExecutor.cs +++ b/src/Umbraco.Infrastructure/Migrations/MigrationPlanExecutor.cs @@ -104,25 +104,19 @@ public class MigrationPlanExecutor : IMigrationPlanExecutor { } + [Obsolete("Use ExecutePlan instead. Scheduled for removal in Umbraco 17.")] public string Execute(MigrationPlan plan, string fromState) => ExecutePlan(plan, fromState).FinalState; - /// - /// Executes the plan. - /// - /// The migration plan to be executes. - /// The state to start execution at. - /// ExecutedMigrationPlan containing information about the plan execution, such as completion state and the steps that ran. - /// - /// Each migration in the plan, may or may not run in a scope depending on the type of plan. - /// A plan can complete partially, the changes of each completed migration will be saved. - /// - public ExecutedMigrationPlan ExecutePlan(MigrationPlan plan, string fromState) + /// + [Obsolete("Use ExecutePlanAsync instead. Scheduled for removal in Umbraco 18.")] + public ExecutedMigrationPlan ExecutePlan(MigrationPlan plan, string fromState) => ExecutePlanAsync(plan, fromState).GetAwaiter().GetResult(); + + /// + public async Task ExecutePlanAsync(MigrationPlan plan, string fromState) { plan.Validate(); - ExecutedMigrationPlan result = RunMigrationPlan(plan, fromState); - - HandlePostMigrations(result); + ExecutedMigrationPlan result = await RunMigrationPlanAsync(plan, fromState).ConfigureAwait(false); // If any completed migration requires us to rebuild cache we'll do that. if (_rebuildCache) @@ -134,40 +128,13 @@ public class MigrationPlanExecutor : IMigrationPlanExecutor // If any completed migration requires us to sign out the user we'll do that. if (_invalidateBackofficeUserAccess) { - RevokeBackofficeTokens().GetAwaiter().GetResult(); // should async all the way up at some point + await RevokeBackofficeTokens().ConfigureAwait(false); } return result; } - [Obsolete] - private void HandlePostMigrations(ExecutedMigrationPlan result) - { - // prepare and de-duplicate post-migrations, only keeping the 1st occurence - var executedTypes = new HashSet(); - - foreach (IMigrationContext executedMigrationContext in result.ExecutedMigrationContexts) - { - if (executedMigrationContext is MigrationContext migrationContext) - { - foreach (Type migrationContextPostMigration in migrationContext.PostMigrations) - { - if (executedTypes.Contains(migrationContextPostMigration)) - { - continue; - } - - _logger.LogInformation("PostMigration: {migrationContextFullName}.", migrationContextPostMigration.FullName); - MigrationBase postMigration = _migrationBuilder.Build(migrationContextPostMigration, executedMigrationContext); - postMigration.Run(); - - executedTypes.Add(migrationContextPostMigration); - } - } - } - } - - private ExecutedMigrationPlan RunMigrationPlan(MigrationPlan plan, string fromState) + private async Task RunMigrationPlanAsync(MigrationPlan plan, string fromState) { _logger.LogInformation("Starting '{MigrationName}'...", plan.Name); var nextState = fromState; @@ -188,13 +155,13 @@ public class MigrationPlanExecutor : IMigrationPlanExecutor try { - if (transition.MigrationType.IsAssignableTo(typeof(UnscopedMigrationBase))) + if (transition.MigrationType.IsAssignableTo(typeof(UnscopedAsyncMigrationBase))) { - executedMigrationContexts.Add(RunUnscopedMigration(transition, plan)); + executedMigrationContexts.Add(await RunUnscopedMigrationAsync(transition, plan).ConfigureAwait(false)); } else { - executedMigrationContexts.Add(RunScopedMigration(transition, plan)); + executedMigrationContexts.Add(await RunScopedMigrationAsync(transition, plan).ConfigureAwait(false)); } } catch (Exception exception) @@ -214,7 +181,6 @@ public class MigrationPlanExecutor : IMigrationPlanExecutor }; } - IEnumerable nonCompletedMigrationsContexts = executedMigrationContexts.Where(x => x.IsCompleted is false); if (nonCompletedMigrationsContexts.Any()) { @@ -270,12 +236,12 @@ public class MigrationPlanExecutor : IMigrationPlanExecutor }; } - private MigrationContext RunUnscopedMigration(MigrationPlan.Transition transition, MigrationPlan plan) + private async Task RunUnscopedMigrationAsync(MigrationPlan.Transition transition, MigrationPlan plan) { using IUmbracoDatabase database = _databaseFactory.CreateDatabase(); var context = new MigrationContext(plan, database, _loggerFactory.CreateLogger(), () => OnComplete(plan, transition.TargetState)); - RunMigration(transition.MigrationType, context); + await RunMigrationAsync(transition.MigrationType, context).ConfigureAwait(false); return context; } @@ -285,7 +251,7 @@ public class MigrationPlanExecutor : IMigrationPlanExecutor _keyValueService.SetValue(Constants.Conventions.Migrations.KeyValuePrefix + plan.Name, targetState); } - private MigrationContext RunScopedMigration(MigrationPlan.Transition transition, MigrationPlan plan) + private async Task RunScopedMigrationAsync(MigrationPlan.Transition transition, MigrationPlan plan) { // We want to suppress scope (service, etc...) notifications during a migration plan // execution. This is because if a package that doesn't have their migration plan @@ -300,7 +266,7 @@ public class MigrationPlanExecutor : IMigrationPlanExecutor _loggerFactory.CreateLogger(), () => OnComplete(plan, transition.TargetState)); - RunMigration(transition.MigrationType, context); + await RunMigrationAsync(transition.MigrationType, context).ConfigureAwait(false); // Ensure we mark the context as complete before the scope completes context.Complete(); @@ -311,10 +277,10 @@ public class MigrationPlanExecutor : IMigrationPlanExecutor } } - private void RunMigration(Type migrationType, MigrationContext context) + private async Task RunMigrationAsync(Type migrationType, MigrationContext context) { - MigrationBase migration = _migrationBuilder.Build(migrationType, context); - migration.Run(); + AsyncMigrationBase migration = _migrationBuilder.Build(migrationType, context); + await migration.RunAsync().ConfigureAwait(false); // If the migration requires clearing the cache set the flag, this will automatically only happen if it succeeds // Otherwise it'll error out before and return. diff --git a/src/Umbraco.Infrastructure/Migrations/NoopMigration.cs b/src/Umbraco.Infrastructure/Migrations/NoopMigration.cs index 9ce64977c0..bc94591dbb 100644 --- a/src/Umbraco.Infrastructure/Migrations/NoopMigration.cs +++ b/src/Umbraco.Infrastructure/Migrations/NoopMigration.cs @@ -1,14 +1,12 @@ + namespace Umbraco.Cms.Infrastructure.Migrations; -public class NoopMigration : MigrationBase +public class NoopMigration : AsyncMigrationBase { public NoopMigration(IMigrationContext context) : base(context) - { - } + { } - protected override void Migrate() - { - // nop - } + protected override Task MigrateAsync() + => Task.CompletedTask; } diff --git a/src/Umbraco.Infrastructure/Migrations/PostMigrations/CacheRebuilder.cs b/src/Umbraco.Infrastructure/Migrations/PostMigrations/CacheRebuilder.cs index 29ba8b3878..618391b4ad 100644 --- a/src/Umbraco.Infrastructure/Migrations/PostMigrations/CacheRebuilder.cs +++ b/src/Umbraco.Infrastructure/Migrations/PostMigrations/CacheRebuilder.cs @@ -7,6 +7,7 @@ namespace Umbraco.Cms.Infrastructure.Migrations.PostMigrations; /// /// Implements in Umbraco.Web (rebuilding). /// +[Obsolete("This is no longer used. Scheduled for removal in Umbraco 17.")] public class CacheRebuilder : ICacheRebuilder { private readonly DistributedCache _distributedCache; diff --git a/src/Umbraco.Infrastructure/Migrations/PostMigrations/ICacheRebuilder.cs b/src/Umbraco.Infrastructure/Migrations/PostMigrations/ICacheRebuilder.cs index ee2e72ee52..5e53a11a53 100644 --- a/src/Umbraco.Infrastructure/Migrations/PostMigrations/ICacheRebuilder.cs +++ b/src/Umbraco.Infrastructure/Migrations/PostMigrations/ICacheRebuilder.cs @@ -10,6 +10,7 @@ namespace Umbraco.Cms.Infrastructure.Migrations.PostMigrations; /// be refactored, really. /// /// +[Obsolete("This is no longer used. Scheduled for removal in Umbraco 17.")] public interface ICacheRebuilder { /// diff --git a/src/Umbraco.Infrastructure/Migrations/UnscopedAsyncMigrationBase.cs b/src/Umbraco.Infrastructure/Migrations/UnscopedAsyncMigrationBase.cs new file mode 100644 index 0000000000..0b5ced41a4 --- /dev/null +++ b/src/Umbraco.Infrastructure/Migrations/UnscopedAsyncMigrationBase.cs @@ -0,0 +1,34 @@ +using Umbraco.Cms.Infrastructure.Scoping; + +namespace Umbraco.Cms.Infrastructure.Migrations; + +/// +/// Base class for creating a migration that does not have a scope provided for it. +/// +public abstract class UnscopedAsyncMigrationBase : AsyncMigrationBase +{ + /// + /// Initializes a new instance of the class. + /// + /// A migration context. + protected UnscopedAsyncMigrationBase(IMigrationContext context) + : base(context) + { } + + /// + /// Scope the database used by the migration builder. + /// This is used with when you need to execute something before the scope is created + /// but later need to have your queries scoped in a transaction. + /// + /// The scope to get the database from. + /// If the migration is missing or has a malformed MigrationContext, this exception is thrown. + protected void ScopeDatabase(IScope scope) + { + if (Context is not MigrationContext context) + { + throw new InvalidOperationException("Cannot scope database because context is not a MigrationContext"); + } + + context.Database = scope.Database; + } +} diff --git a/src/Umbraco.Infrastructure/Migrations/UnscopedMigrationBase.cs b/src/Umbraco.Infrastructure/Migrations/UnscopedMigrationBase.cs index 910b9753de..a182d22370 100644 --- a/src/Umbraco.Infrastructure/Migrations/UnscopedMigrationBase.cs +++ b/src/Umbraco.Infrastructure/Migrations/UnscopedMigrationBase.cs @@ -1,31 +1,26 @@ -using Umbraco.Cms.Infrastructure.Scoping; - namespace Umbraco.Cms.Infrastructure.Migrations; -/// -/// Base class for creating a migration that does not have a scope provided for it. -/// -public abstract class UnscopedMigrationBase : MigrationBase +/// +public abstract class UnscopedMigrationBase : UnscopedAsyncMigrationBase { + /// + /// Initializes a new instance of the class. + /// + /// The context. protected UnscopedMigrationBase(IMigrationContext context) : base(context) + { } + + /// + protected override Task MigrateAsync() { + Migrate(); + + return Task.CompletedTask; } /// - /// Scope the database used by the migration builder. - /// This is used with when you need to execute something before the scope is created - /// but later need to have your queries scoped in a transaction. + /// Executes the migration. /// - /// The scope to get the database from. - /// If the migration is missing or has a malformed MigrationContext, this exception is thrown. - protected void ScopeDatabase(IScope scope) - { - if (Context is not MigrationContext context) - { - throw new InvalidOperationException("Cannot scope database because context is not a MigrationContext"); - } - - context.Database = scope.Database; - } + protected abstract void Migrate(); } diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/Upgrader.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/Upgrader.cs index b73967f400..921a6f95f7 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/Upgrader.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/Upgrader.cs @@ -30,13 +30,20 @@ public class Upgrader /// public virtual string StateValueKey => Constants.Conventions.Migrations.KeyValuePrefix + Name; + [Obsolete("Use ExecuteAsync instead. Scheduled for removal in Umbraco 18.")] + public ExecutedMigrationPlan Execute( + IMigrationPlanExecutor migrationPlanExecutor, + ICoreScopeProvider scopeProvider, + IKeyValueService keyValueService) + => ExecuteAsync(migrationPlanExecutor, scopeProvider, keyValueService).GetAwaiter().GetResult(); + /// /// Executes. /// /// /// A scope provider. /// A key-value service. - public ExecutedMigrationPlan Execute( + public async Task ExecuteAsync( IMigrationPlanExecutor migrationPlanExecutor, ICoreScopeProvider scopeProvider, IKeyValueService keyValueService) @@ -53,7 +60,7 @@ public class Upgrader string initialState = GetInitialState(scopeProvider, keyValueService); - ExecutedMigrationPlan result = migrationPlanExecutor.ExecutePlan(Plan, initialState); + ExecutedMigrationPlan result = await migrationPlanExecutor.ExecutePlanAsync(Plan, initialState).ConfigureAwait(false); // This should never happen, if the final state comes back as null or equal to the initial state // it means that no transitions was successful, which means it cannot be a successful migration @@ -86,11 +93,4 @@ public class Upgrader return currentState; } - - private void SetState(string state, ICoreScopeProvider scopeProvider, IKeyValueService keyValueService) - { - using ICoreScope scope = scopeProvider.CreateCoreScope(); - keyValueService.SetValue(StateValueKey, state); - scope.Complete(); - } } diff --git a/src/Umbraco.Infrastructure/Packaging/AsyncPackageMigrationBase.cs b/src/Umbraco.Infrastructure/Packaging/AsyncPackageMigrationBase.cs new file mode 100644 index 0000000000..e9bb8df92f --- /dev/null +++ b/src/Umbraco.Infrastructure/Packaging/AsyncPackageMigrationBase.cs @@ -0,0 +1,51 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Strings; +using Umbraco.Cms.Infrastructure.Migrations; + +namespace Umbraco.Cms.Infrastructure.Packaging; + +public abstract class AsyncPackageMigrationBase : AsyncMigrationBase +{ + private readonly IContentTypeBaseServiceProvider _contentTypeBaseServiceProvider; + private readonly MediaFileManager _mediaFileManager; + private readonly IMediaService _mediaService; + private readonly MediaUrlGeneratorCollection _mediaUrlGenerators; + private readonly IOptions _packageMigrationsSettings; + private readonly IPackagingService _packagingService; + private readonly IShortStringHelper _shortStringHelper; + + public AsyncPackageMigrationBase( + IPackagingService packagingService, + IMediaService mediaService, + MediaFileManager mediaFileManager, + MediaUrlGeneratorCollection mediaUrlGenerators, + IShortStringHelper shortStringHelper, + IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, + IMigrationContext context, + IOptions packageMigrationsSettings) + : base(context) + { + _packagingService = packagingService; + _mediaService = mediaService; + _mediaFileManager = mediaFileManager; + _mediaUrlGenerators = mediaUrlGenerators; + _shortStringHelper = shortStringHelper; + _contentTypeBaseServiceProvider = contentTypeBaseServiceProvider; + _packageMigrationsSettings = packageMigrationsSettings; + } + + public IImportPackageBuilder ImportPackage => BeginBuild( + new ImportPackageBuilder( + _packagingService, + _mediaService, + _mediaFileManager, + _mediaUrlGenerators, + _shortStringHelper, + _contentTypeBaseServiceProvider, + Context, + _packageMigrationsSettings)); +} diff --git a/src/Umbraco.Infrastructure/Packaging/AutomaticPackageMigrationPlan.cs b/src/Umbraco.Infrastructure/Packaging/AutomaticPackageMigrationPlan.cs index d8becf0bfa..59a921364f 100644 --- a/src/Umbraco.Infrastructure/Packaging/AutomaticPackageMigrationPlan.cs +++ b/src/Umbraco.Infrastructure/Packaging/AutomaticPackageMigrationPlan.cs @@ -21,8 +21,7 @@ public abstract class AutomaticPackageMigrationPlan : PackageMigrationPlan /// The package name that the plan is for. If the package has a package.manifest these must match. protected AutomaticPackageMigrationPlan(string packageName) : this(packageName, packageName) - { - } + { } /// /// Initializes a new instance of the class. @@ -31,8 +30,7 @@ public abstract class AutomaticPackageMigrationPlan : PackageMigrationPlan /// The plan name for the package. This should be the same name as the package name, if there is only one plan in the package. protected AutomaticPackageMigrationPlan(string packageName, string planName) : this(null!, packageName, planName) - { - } + { } /// /// Initializes a new instance of the class. @@ -42,8 +40,7 @@ public abstract class AutomaticPackageMigrationPlan : PackageMigrationPlan /// The plan name for the package. This should be the same name as the package name, if there is only one plan in the package. protected AutomaticPackageMigrationPlan(string packageId, string packageName, string planName) : base(packageId, packageName, planName) - { - } + { } /// protected sealed override void DefinePlan() @@ -59,7 +56,7 @@ public abstract class AutomaticPackageMigrationPlan : PackageMigrationPlan /// /// Provides a migration that imports an embedded package data manifest. /// - private class MigrateToPackageData : PackageMigrationBase + private sealed class MigrateToPackageData : AsyncPackageMigrationBase { /// /// Initializes a new instance of the class. @@ -82,15 +79,16 @@ public abstract class AutomaticPackageMigrationPlan : PackageMigrationPlan IMigrationContext context, IOptions options) : base(packagingService, mediaService, mediaFileManager, mediaUrlGenerators, shortStringHelper, contentTypeBaseServiceProvider, context, options) - { - } + { } /// - protected override void Migrate() + protected override Task MigrateAsync() { var plan = (AutomaticPackageMigrationPlan)Context.Plan; ImportPackage.FromEmbeddedResource(plan.GetType()).Do(); + + return Task.CompletedTask; } } } diff --git a/src/Umbraco.Infrastructure/Packaging/IImportPackageBuilder.cs b/src/Umbraco.Infrastructure/Packaging/IImportPackageBuilder.cs index f826dd9dfe..8265fa5d14 100644 --- a/src/Umbraco.Infrastructure/Packaging/IImportPackageBuilder.cs +++ b/src/Umbraco.Infrastructure/Packaging/IImportPackageBuilder.cs @@ -7,7 +7,7 @@ namespace Umbraco.Cms.Infrastructure.Packaging; public interface IImportPackageBuilder : IFluentBuilder { IExecutableBuilder FromEmbeddedResource() - where TPackageMigration : PackageMigrationBase; + where TPackageMigration : AsyncPackageMigrationBase; IExecutableBuilder FromEmbeddedResource(Type packageMigrationType); diff --git a/src/Umbraco.Infrastructure/Packaging/ImportPackageBuilder.cs b/src/Umbraco.Infrastructure/Packaging/ImportPackageBuilder.cs index 8b28628e4c..e557181a51 100644 --- a/src/Umbraco.Infrastructure/Packaging/ImportPackageBuilder.cs +++ b/src/Umbraco.Infrastructure/Packaging/ImportPackageBuilder.cs @@ -38,7 +38,7 @@ internal class ImportPackageBuilder : ExpressionBuilderBase Expression.Execute(); public IExecutableBuilder FromEmbeddedResource() - where TPackageMigration : PackageMigrationBase + where TPackageMigration : AsyncPackageMigrationBase { Expression.EmbeddedResourceMigrationType = typeof(TPackageMigration); return this; diff --git a/src/Umbraco.Infrastructure/Packaging/PackageMigrationBase.cs b/src/Umbraco.Infrastructure/Packaging/PackageMigrationBase.cs index 6f0355f674..9bd820a1e9 100644 --- a/src/Umbraco.Infrastructure/Packaging/PackageMigrationBase.cs +++ b/src/Umbraco.Infrastructure/Packaging/PackageMigrationBase.cs @@ -8,17 +8,11 @@ using Umbraco.Cms.Infrastructure.Migrations; namespace Umbraco.Cms.Infrastructure.Packaging; -public abstract class PackageMigrationBase : MigrationBase +/// +[Obsolete("Use AsyncPackageMigrationBase instead. Scheduled for removal in Umbraco 18.")] +public abstract class PackageMigrationBase : AsyncPackageMigrationBase { - private readonly IContentTypeBaseServiceProvider _contentTypeBaseServiceProvider; - private readonly MediaFileManager _mediaFileManager; - private readonly IMediaService _mediaService; - private readonly MediaUrlGeneratorCollection _mediaUrlGenerators; - private readonly IOptions _packageMigrationsSettings; - private readonly IPackagingService _packagingService; - private readonly IShortStringHelper _shortStringHelper; - - public PackageMigrationBase( + protected PackageMigrationBase( IPackagingService packagingService, IMediaService mediaService, MediaFileManager mediaFileManager, @@ -27,27 +21,19 @@ public abstract class PackageMigrationBase : MigrationBase IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, IMigrationContext context, IOptions packageMigrationsSettings) - : base(context) + : base(packagingService, mediaService, mediaFileManager, mediaUrlGenerators, shortStringHelper, contentTypeBaseServiceProvider, context, packageMigrationsSettings) + { } + + /// + protected override Task MigrateAsync() { - _packagingService = packagingService; - _mediaService = mediaService; - _mediaFileManager = mediaFileManager; - _mediaUrlGenerators = mediaUrlGenerators; - _shortStringHelper = shortStringHelper; - _contentTypeBaseServiceProvider = contentTypeBaseServiceProvider; - _packageMigrationsSettings = packageMigrationsSettings; + Migrate(); + + return Task.CompletedTask; } - public IImportPackageBuilder ImportPackage => BeginBuild( - new ImportPackageBuilder( - _packagingService, - _mediaService, - _mediaFileManager, - _mediaUrlGenerators, - _shortStringHelper, - _contentTypeBaseServiceProvider, - Context, - _packageMigrationsSettings)); - - + /// + /// Executes the migration. + /// + protected abstract void Migrate(); } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/RuntimeStateTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/RuntimeStateTests.cs index 208b7b11e4..09408824a3 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Core/RuntimeStateTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/RuntimeStateTests.cs @@ -79,7 +79,7 @@ public class RuntimeStateTests : UmbracoIntegrationTest protected override void DefinePlan() => To(TestMigrationFinalState); } - private class TestMigration : PackageMigrationBase + private sealed class TestMigration : AsyncPackageMigrationBase { public TestMigration( IPackagingService packagingService, @@ -91,9 +91,13 @@ public class RuntimeStateTests : UmbracoIntegrationTest IMigrationContext context, IOptions options) : base(packagingService, mediaService, mediaFileManager, mediaUrlGenerators, shortStringHelper, contentTypeBaseServiceProvider, context, options) - { - } + { } - protected override void Migrate() => ImportPackage.FromEmbeddedResource().Do(); + protected override Task MigrateAsync() + { + ImportPackage.FromEmbeddedResource().Do(); + + return Task.CompletedTask; + } } } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Migrations/AdvancedMigrationTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Migrations/AdvancedMigrationTests.cs index 35194277c2..bf17957aad 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Migrations/AdvancedMigrationTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Migrations/AdvancedMigrationTests.cs @@ -47,10 +47,11 @@ public class AdvancedMigrationTests : UmbracoIntegrationTest DatabaseCacheRebuilder, DistributedCache, Mock.Of(), - ServiceScopeFactory); + ServiceScopeFactory, + AppCaches.NoCache); [Test] - public void CreateTableOfTDto() + public async Task CreateTableOfTDtoAsync() { var builder = Mock.Of(); Mock.Get(builder) @@ -72,7 +73,7 @@ public class AdvancedMigrationTests : UmbracoIntegrationTest .From(string.Empty) .To("done")); - upgrader.Execute(MigrationPlanExecutor, ScopeProvider, Mock.Of()); + await upgrader.ExecuteAsync(MigrationPlanExecutor, ScopeProvider, Mock.Of()).ConfigureAwait(false); var db = ScopeAccessor.AmbientScope.Database; var exists = ScopeAccessor.AmbientScope.SqlContext.SqlSyntax.DoesTableExist(db, "umbracoUser"); @@ -82,7 +83,7 @@ public class AdvancedMigrationTests : UmbracoIntegrationTest } [Test] - public void DeleteKeysAndIndexesOfTDto() + public async Task DeleteKeysAndIndexesOfTDtoAsync() { var builder = Mock.Of(); Mock.Get(builder) @@ -108,13 +109,13 @@ public class AdvancedMigrationTests : UmbracoIntegrationTest .To("a") .To("done")); - upgrader.Execute(MigrationPlanExecutor, ScopeProvider, Mock.Of()); + await upgrader.ExecuteAsync(MigrationPlanExecutor, ScopeProvider, Mock.Of()).ConfigureAwait(false); scope.Complete(); } } [Test] - public void CreateKeysAndIndexesOfTDto() + public async Task CreateKeysAndIndexesOfTDtoAsync() { if (BaseTestDatabase.IsSqlite()) { @@ -150,13 +151,13 @@ public class AdvancedMigrationTests : UmbracoIntegrationTest .To("b") .To("done")); - upgrader.Execute(MigrationPlanExecutor, ScopeProvider, Mock.Of()); + await upgrader.ExecuteAsync(MigrationPlanExecutor, ScopeProvider, Mock.Of()).ConfigureAwait(false); scope.Complete(); } } [Test] - public void CreateKeysAndIndexes() + public async Task CreateKeysAndIndexesAsync() { if (BaseTestDatabase.IsSqlite()) { @@ -192,13 +193,13 @@ public class AdvancedMigrationTests : UmbracoIntegrationTest .To("b") .To("done")); - upgrader.Execute(MigrationPlanExecutor, ScopeProvider, Mock.Of()); + await upgrader.ExecuteAsync(MigrationPlanExecutor, ScopeProvider, Mock.Of()).ConfigureAwait(false); scope.Complete(); } } [Test] - public void AddColumn() + public async Task AddColumnAsync() { var builder = Mock.Of(); Mock.Get(builder) @@ -224,7 +225,7 @@ public class AdvancedMigrationTests : UmbracoIntegrationTest .To("a") .To("done")); - upgrader.Execute(MigrationPlanExecutor, ScopeProvider, Mock.Of()); + await upgrader.ExecuteAsync(MigrationPlanExecutor, ScopeProvider, Mock.Of()).ConfigureAwait(false); var db = ScopeAccessor.AmbientScope.Database; diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Migrations/PartialMigrationsTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Migrations/PartialMigrationsTests.cs index 1fb0e78417..dc0d4b0a4d 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Migrations/PartialMigrationsTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Migrations/PartialMigrationsTests.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using NPoco; using NUnit.Framework; @@ -42,7 +42,7 @@ public class PartialMigrationsTests : UmbracoIntegrationTest => services.AddNotificationHandler(); [Test] - public void CanRerunPartiallyCompletedMigration() + public async Task CanRerunPartiallyCompletedMigration() { var plan = new MigrationPlan("test") .From(string.Empty) @@ -52,7 +52,7 @@ public class PartialMigrationsTests : UmbracoIntegrationTest var upgrader = new Upgrader(plan); - var result = upgrader.Execute(MigrationPlanExecutor, ScopeProvider, KeyValueService); + var result = await upgrader.ExecuteAsync(MigrationPlanExecutor, ScopeProvider, KeyValueService).ConfigureAwait(false); Assert.Multiple(() => { @@ -74,7 +74,7 @@ public class PartialMigrationsTests : UmbracoIntegrationTest // Now let's simulate that someone came along and fixed the broken migration and we'll now try and rerun ErrorMigration.ShouldExplode = false; upgrader = new Upgrader(plan); - result = upgrader.Execute(MigrationPlanExecutor, ScopeProvider, KeyValueService); + result = await upgrader.ExecuteAsync(MigrationPlanExecutor, ScopeProvider, KeyValueService).ConfigureAwait(false); Assert.Multiple(() => { @@ -93,12 +93,12 @@ public class PartialMigrationsTests : UmbracoIntegrationTest } [Test] - public void CanRunMigrationTwice() + public async Task CanRunMigrationTwice() { Upgrader? upgrader = new(new SimpleMigrationPlan()); Upgrader? upgrader2 = new(new SimpleMigrationPlan()); - var result = upgrader.Execute(MigrationPlanExecutor, ScopeProvider, KeyValueService); - var result2 = upgrader2.Execute(MigrationPlanExecutor, ScopeProvider, KeyValueService); + var result = await upgrader.ExecuteAsync(MigrationPlanExecutor, ScopeProvider, KeyValueService).ConfigureAwait(false); + var result2 = await upgrader2.ExecuteAsync(MigrationPlanExecutor, ScopeProvider, KeyValueService).ConfigureAwait(false); Assert.Multiple(() => { @@ -113,7 +113,7 @@ public class PartialMigrationsTests : UmbracoIntegrationTest } [Test] - public void StateIsOnlySavedIfAMigrationSucceeds() + public async Task StateIsOnlySavedIfAMigrationSucceeds() { var plan = new MigrationPlan("test") .From(string.Empty) @@ -121,7 +121,7 @@ public class PartialMigrationsTests : UmbracoIntegrationTest .To("b"); var upgrader = new Upgrader(plan); - var result = upgrader.Execute(MigrationPlanExecutor, ScopeProvider, KeyValueService); + var result = await upgrader.ExecuteAsync(MigrationPlanExecutor, ScopeProvider, KeyValueService).ConfigureAwait(false); Assert.Multiple(() => { @@ -138,16 +138,16 @@ public class PartialMigrationsTests : UmbracoIntegrationTest } [Test] - public void ScopesAreCreatedIfNecessary() + public async Task ScopesAreCreatedIfNecessary() { - // The migrations have assert to esnure scopes + // The migrations have assert to ensure scopes var plan = new MigrationPlan("test") .From(string.Empty) .To("a") .To("b"); var upgrader = new Upgrader(plan); - var result = upgrader.Execute(MigrationPlanExecutor, ScopeProvider, KeyValueService); + var result = await upgrader.ExecuteAsync(MigrationPlanExecutor, ScopeProvider, KeyValueService).ConfigureAwait(false); Assert.IsTrue(result.Successful); Assert.AreEqual(2, result.CompletedTransitions.Count); @@ -157,7 +157,7 @@ public class PartialMigrationsTests : UmbracoIntegrationTest [Test] [TestCase(true)] [TestCase(false)] - public void UmbracoPlanExecutedNotificationIsAlwaysPublished(bool shouldSucceed) + public async Task UmbracoPlanExecutedNotificationIsAlwaysPublished(bool shouldSucceed) { var notificationPublished = false; ErrorMigration.ShouldExplode = shouldSucceed is false; @@ -190,7 +190,7 @@ public class PartialMigrationsTests : UmbracoIntegrationTest // We have to use the DatabaseBuilder otherwise the notification isn't published var databaseBuilder = GetRequiredService(); var plan = new TestUmbracoPlan(null!); - databaseBuilder.UpgradeSchemaAndData(plan); + await databaseBuilder.UpgradeSchemaAndDataAsync(plan).ConfigureAwait(false); Assert.IsTrue(notificationPublished); } @@ -242,7 +242,7 @@ internal class AddColumnMigration : MigrationBase .Do(); } -internal class AssertScopeUnscopedTestMigration : UnscopedMigrationBase +internal class AssertScopeUnscopedTestMigration : UnscopedAsyncMigrationBase { private readonly IScopeProvider _scopeProvider; private readonly IScopeAccessor _scopeAccessor; @@ -256,7 +256,7 @@ internal class AssertScopeUnscopedTestMigration : UnscopedMigrationBase _scopeAccessor = scopeAccessor; } - protected override void Migrate() + protected override Task MigrateAsync() { // Since this is a scopeless migration both ambient scope and the parent scope should be null Assert.IsNull(_scopeAccessor.AmbientScope); @@ -265,6 +265,8 @@ internal class AssertScopeUnscopedTestMigration : UnscopedMigrationBase Assert.IsNull(((Scope)scope).ParentScope); Context.Complete(); + + return Task.CompletedTask; } } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/AlterMigrationTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/AlterMigrationTests.cs index 516e7f80c1..e8f79d7da7 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/AlterMigrationTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/AlterMigrationTests.cs @@ -32,14 +32,14 @@ public class AlterMigrationTests } [Test] - public void Drop_Foreign_Key() + public async Task Drop_Foreign_Key() { // Arrange var context = GetMigrationContext(out var database); var stub = new DropForeignKeyMigrationStub(context); // Act - stub.Run(); + await stub.RunAsync().ConfigureAwait(false); foreach (var op in database.Operations) { @@ -48,18 +48,16 @@ public class AlterMigrationTests // Assert Assert.That(database.Operations.Count, Is.EqualTo(1)); - Assert.That( - database.Operations[0].Sql, - Is.EqualTo("ALTER TABLE [umbracoUser2app] DROP CONSTRAINT [FK_umbracoUser2app_umbracoUser_id]")); + Assert.That(database.Operations[0].Sql, Is.EqualTo("ALTER TABLE [umbracoUser2app] DROP CONSTRAINT [FK_umbracoUser2app_umbracoUser_id]")); } [Test] - public void CreateColumn() + public async Task CreateColumn() { var context = GetMigrationContext(out var database); var migration = new CreateColumnMigration(context); - migration.Run(); + await migration.RunAsync().ConfigureAwait(false); foreach (var op in database.Operations) { @@ -83,12 +81,12 @@ public class AlterMigrationTests } [Test] - public void AlterColumn() + public async Task AlterColumn() { var context = GetMigrationContext(out var database); var migration = new AlterColumnMigration(context); - migration.Run(); + await migration.RunAsync().ConfigureAwait(false); foreach (var op in database.Operations) { @@ -117,14 +115,14 @@ public class AlterMigrationTests [Ignore("this doesn't actually test anything")] [Test] - public void Can_Get_Up_Migration_From_MigrationStub() + public async Task Can_Get_Up_Migration_From_MigrationStub() { // Arrange var context = GetMigrationContext(out var database); var stub = new AlterUserTableMigrationStub(context); // Act - stub.Run(); + await stub.RunAsync().ConfigureAwait(false); // Assert Assert.That(database.Operations.Any(), Is.True); diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/MigrationPlanTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/MigrationPlanTests.cs index 449272fbf7..261d41128d 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/MigrationPlanTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/MigrationPlanTests.cs @@ -30,7 +30,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Migrations; public class MigrationPlanTests { [Test] - public void CanExecute() + public async Task CanExecute() { var loggerFactory = NullLoggerFactory.Instance; @@ -81,7 +81,11 @@ public class MigrationPlanTests loggerFactory, migrationBuilder, databaseFactory, - Mock.Of(), distributedCache, Mock.Of(), Mock.Of(), appCaches); + Mock.Of(), + distributedCache, + Mock.Of(), + Mock.Of(), + appCaches); var plan = new MigrationPlan("default") .From(string.Empty) @@ -99,7 +103,7 @@ public class MigrationPlanTests var sourceState = kvs.GetValue("Umbraco.Tests.MigrationPlan") ?? string.Empty; // execute plan - var result = executor.ExecutePlan(plan, sourceState); + var result = await executor.ExecutePlanAsync(plan, sourceState).ConfigureAwait(false); state = result.FinalState; // save new state diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/MigrationTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/MigrationTests.cs index f3ea130a68..135e5406eb 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/MigrationTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/MigrationTests.cs @@ -77,11 +77,11 @@ public class MigrationTests Mock.Of>()); [Test] - public void RunGoodMigration() + public async Task RunGoodMigration() { var migrationContext = GetMigrationContext(); MigrationBase migration = new GoodMigration(migrationContext); - migration.Run(); + await migration.RunAsync(); } [Test] @@ -89,7 +89,7 @@ public class MigrationTests { var migrationContext = GetMigrationContext(); MigrationBase migration = new BadMigration1(migrationContext); - Assert.Throws(() => migration.Run()); + Assert.ThrowsAsync(migration.RunAsync); } [Test] @@ -97,7 +97,7 @@ public class MigrationTests { var migrationContext = GetMigrationContext(); MigrationBase migration = new BadMigration2(migrationContext); - Assert.Throws(() => migration.Run()); + Assert.ThrowsAsync(migration.RunAsync); } public class GoodMigration : MigrationBase