From d9d5bf651b996827a26ab6fa75d162f7cf92e071 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Sat, 14 Sep 2024 09:27:21 +0200 Subject: [PATCH] Add AsyncMigrationBase, update base classes and call async methods --- .../Install/PackageMigrationRunner.cs | 13 +- .../Install/PremigrationUpgrader.cs | 22 +-- .../Install/UnattendedUpgrader.cs | 41 ++--- .../Installer/Steps/DatabaseUpgradeStep.cs | 10 +- .../Migrations/AsyncMigrationBase.Database.cs | 147 +++++++++++++++++ .../Migrations/AsyncMigrationBase.cs | 140 ++++++++++++++++ .../Migrations/IMigrationBuilder.cs | 2 +- .../Migrations/IMigrationContext.cs | 2 +- .../Migrations/IMigrationPlanExecutor.cs | 12 +- .../Migrations/Install/DatabaseBuilder.cs | 6 +- .../Migrations/MergeBuilder.cs | 2 +- .../Migrations/MigrationBase.cs | 136 ++-------------- .../Migrations/MigrationBase_Extra.cs | 151 ------------------ .../Migrations/MigrationBuilder.cs | 4 +- .../Migrations/MigrationContext.cs | 2 +- .../Migrations/MigrationPlan.cs | 14 +- .../Migrations/MigrationPlanExecutor.cs | 39 +++-- .../Migrations/NoopMigration.cs | 12 +- .../Migrations/UnscopedAsyncMigrationBase.cs | 34 ++++ .../Migrations/UnscopedMigrationBase.cs | 35 ++-- .../Migrations/Upgrade/Upgrader.cs | 11 +- .../Packaging/AsyncPackageMigrationBase.cs | 51 ++++++ .../AutomaticPackageMigrationPlan.cs | 18 +-- .../Packaging/IImportPackageBuilder.cs | 2 +- .../Packaging/ImportPackageBuilder.cs | 2 +- .../Packaging/PackageMigrationBase.cs | 46 ++---- .../Umbraco.Core/RuntimeStateTests.cs | 13 +- .../Migrations/AdvancedMigrationTests.cs | 20 +-- .../Migrations/PartialMigrationsTests.cs | 34 ++-- .../Migrations/AlterMigrationTests.cs | 20 ++- .../Migrations/MigrationPlanTests.cs | 9 +- .../Migrations/MigrationTests.cs | 8 +- 32 files changed, 552 insertions(+), 506 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/Install/PackageMigrationRunner.cs b/src/Umbraco.Infrastructure/Install/PackageMigrationRunner.cs index 954ff0a9aa01..a9a7062f376c 100644 --- a/src/Umbraco.Infrastructure/Install/PackageMigrationRunner.cs +++ b/src/Umbraco.Infrastructure/Install/PackageMigrationRunner.cs @@ -55,10 +55,9 @@ public PackageMigrationRunner( /// /// /// - 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,7 +65,7 @@ public IEnumerable RunPackageMigrationsIfPending(string p .Where(x => pendingMigrations.Contains(x.Name)) .Select(x => x.Name); - return RunPackagePlans(packagePlans); + return await RunPackagePlansAsync(packagePlans).ConfigureAwait(false); } /// @@ -81,7 +80,7 @@ public async Task> RunPendingPack } // Run the migrations - IEnumerable executedMigrationPlans = RunPackageMigrationsIfPending(packageName); + IEnumerable executedMigrationPlans = await RunPackageMigrationsIfPendingAsync(packageName).ConfigureAwait(false); if (executedMigrationPlans.Any(plan => plan.Successful == false)) { @@ -98,7 +97,7 @@ public async Task> RunPendingPack /// /// /// 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 +119,7 @@ public IEnumerable RunPackagePlans(IEnumerable pl 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 a3307a347b2d..0584fe3fee84 100644 --- a/src/Umbraco.Infrastructure/Install/PremigrationUpgrader.cs +++ b/src/Umbraco.Infrastructure/Install/PremigrationUpgrader.cs @@ -36,42 +36,36 @@ public PremigrationUpgrader( _keyValueService = keyValueService; } - public Task HandleAsync(RuntimePremigrationsUpgradeNotification notification, CancellationToken cancellationToken) + public async Task HandleAsync(RuntimePremigrationsUpgradeNotification notification, CancellationToken cancellationToken) { // no connection string set if (_umbracoDatabaseFactory.Configured is false) { - return Task.CompletedTask; + return; } if (_databaseBuilder.IsUmbracoInstalled() is false) { - return Task.CompletedTask; + return; } var plan = new UmbracoPremigrationPlan(); if (HasMissingPremigrations(plan) is false) { - return Task.CompletedTask; + return; } - using (_profilingLogger.IsEnabled(LogLevel.Verbose) is false ? null : _profilingLogger.TraceDuration( - "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 8ad2b07b2310..412ad8619a7e 100644 --- a/src/Umbraco.Infrastructure/Install/UnattendedUpgrader.cs +++ b/src/Umbraco.Infrastructure/Install/UnattendedUpgrader.cs @@ -38,7 +38,7 @@ public UnattendedUpgrader( _packageMigrationRunner = packageMigrationRunner; } - public Task HandleAsync(RuntimeUnattendedUpgradeNotification notification, CancellationToken cancellationToken) + public async Task HandleAsync(RuntimeUnattendedUpgradeNotification notification, CancellationToken cancellationToken) { if (_runtimeState.RunUnattendedBootLogic()) { @@ -47,53 +47,41 @@ public Task HandleAsync(RuntimeUnattendedUpgradeNotification notification, Cance case RuntimeLevelReason.UpgradeMigrations: { var plan = new UmbracoPlan(_umbracoVersion); - using (!_profilingLogger.IsEnabled(Core.Logging.LogLevel.Verbose) ? null : _profilingLogger.TraceDuration( - "Starting unattended upgrade.", - "Unattended upgrade completed.")) + using (!_profilingLogger.IsEnabled(LogLevel.Verbose) ? null : _profilingLogger.TraceDuration("Starting unattended upgrade.", "Unattended upgrade completed.")) { - DatabaseBuilder.Result? result = _databaseBuilder.UpgradeSchemaAndData(plan); + DatabaseBuilder.Result? result = await _databaseBuilder.UpgradeSchemaAndDataAsync(plan).ConfigureAwait(false); if (result?.Success == false) { - var innerException = new UnattendedInstallException( - "An error occurred while running the unattended upgrade.\n" + result.Message); + var innerException = new UnattendedInstallException($"An error occurred while running the unattended upgrade.\n{result.Message}"); _runtimeState.Configure(RuntimeLevel.BootFailed, RuntimeLevelReason.BootFailedOnException, innerException); } - notification.UnattendedUpgradeResult = - RuntimeUnattendedUpgradeNotification.UpgradeResult.CoreUpgradeComplete; + notification.UnattendedUpgradeResult = RuntimeUnattendedUpgradeNotification.UpgradeResult.CoreUpgradeComplete; } } break; case RuntimeLevelReason.UpgradePackageMigrations: { - if (!_runtimeState.StartupState.TryGetValue( - RuntimeState.PendingPackageMigrationsStateKey, - out var pm) - || pm is not IReadOnlyList pendingMigrations) + if (!_runtimeState.StartupState.TryGetValue(RuntimeState.PendingPackageMigrationsStateKey, out var pm) || pm is not IReadOnlyList pendingMigrations) { - throw new InvalidOperationException( - $"The required key {RuntimeState.PendingPackageMigrationsStateKey} does not exist in startup state"); + throw new InvalidOperationException($"The required key {RuntimeState.PendingPackageMigrationsStateKey} does not exist in startup state"); } if (pendingMigrations.Count == 0) { - throw new InvalidOperationException( - "No pending migrations found but the runtime level reason is " + - RuntimeLevelReason.UpgradePackageMigrations); + throw new InvalidOperationException($"No pending migrations found but the runtime level reason is {RuntimeLevelReason.UpgradePackageMigrations}"); } try { - _packageMigrationRunner.RunPackagePlans(pendingMigrations); - notification.UnattendedUpgradeResult = RuntimeUnattendedUpgradeNotification.UpgradeResult - .PackageMigrationComplete; + await _packageMigrationRunner.RunPackagePlansAsync(pendingMigrations).ConfigureAwait(false); + notification.UnattendedUpgradeResult = RuntimeUnattendedUpgradeNotification.UpgradeResult.PackageMigrationComplete; } catch (Exception ex) { SetRuntimeError(ex); - notification.UnattendedUpgradeResult = - RuntimeUnattendedUpgradeNotification.UpgradeResult.HasErrors; + notification.UnattendedUpgradeResult = RuntimeUnattendedUpgradeNotification.UpgradeResult.HasErrors; } } @@ -102,13 +90,8 @@ public Task HandleAsync(RuntimeUnattendedUpgradeNotification notification, Cance throw new InvalidOperationException("Invalid reason " + _runtimeState.Reason); } } - - return Task.CompletedTask; } private void SetRuntimeError(Exception exception) - => _runtimeState.Configure( - RuntimeLevel.BootFailed, - RuntimeLevelReason.BootFailedOnException, - exception); + => _runtimeState.Configure(RuntimeLevel.BootFailed, RuntimeLevelReason.BootFailedOnException, exception); } diff --git a/src/Umbraco.Infrastructure/Installer/Steps/DatabaseUpgradeStep.cs b/src/Umbraco.Infrastructure/Installer/Steps/DatabaseUpgradeStep.cs index 4d1e99a27e2b..7bebc3d4fab5 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 DatabaseUpgradeStep( 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 000000000000..21ea92377981 --- /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 000000000000..461a79a0b471 --- /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 078bfcaf387b..8379404c0571 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 cf68617315ba..b95da00c4854 100644 --- a/src/Umbraco.Infrastructure/Migrations/IMigrationContext.cs +++ b/src/Umbraco.Infrastructure/Migrations/IMigrationContext.cs @@ -43,7 +43,7 @@ public interface IMigrationContext /// [Obsolete("This will be removed in the V13, and replaced with a RebuildCache flag on the MigrationBase")] void AddPostMigration() - where TMigration : MigrationBase; + where TMigration : AsyncMigrationBase; bool IsCompleted { get; } diff --git a/src/Umbraco.Infrastructure/Migrations/IMigrationPlanExecutor.cs b/src/Umbraco.Infrastructure/Migrations/IMigrationPlanExecutor.cs index ee3f787c123a..b7e10fe1894f 100644 --- a/src/Umbraco.Infrastructure/Migrations/IMigrationPlanExecutor.cs +++ b/src/Umbraco.Infrastructure/Migrations/IMigrationPlanExecutor.cs @@ -1,18 +1,8 @@ using Umbraco.Cms.Infrastructure.Migrations; -using Umbraco.Extensions; namespace Umbraco.Cms.Core.Migrations; public interface IMigrationPlanExecutor { - [Obsolete("Use ExecutePlan instead.")] - string Execute(MigrationPlan plan, string fromState); - - ExecutedMigrationPlan ExecutePlan(MigrationPlan plan, string fromState) - { - var state = Execute(plan, fromState); - - // 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()); - } + Task ExecutePlanAsync(MigrationPlan plan, string fromState); } diff --git a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseBuilder.cs b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseBuilder.cs index e2930e49bde1..e1f75e02706e 100644 --- a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseBuilder.cs +++ b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseBuilder.cs @@ -347,7 +347,7 @@ private void Configure(bool installMissingDatabase) } } - public Result? UpgradeSchemaAndData(UmbracoPlan plan) => UpgradeSchemaAndData((MigrationPlan)plan); + public async Task UpgradeSchemaAndDataAsync(UmbracoPlan plan) => await UpgradeSchemaAndDataAsync((MigrationPlan)plan).ConfigureAwait(false); /// /// Upgrades the database schema and data by running migrations. @@ -357,7 +357,7 @@ private void Configure(bool installMissingDatabase) /// 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 { @@ -371,7 +371,7 @@ private void Configure(bool installMissingDatabase) // 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 3e80d9ebc5da..9e382022d29e 100644 --- a/src/Umbraco.Infrastructure/Migrations/MergeBuilder.cs +++ b/src/Umbraco.Infrastructure/Migrations/MergeBuilder.cs @@ -25,7 +25,7 @@ public MergeBuilder To(string targetState) /// 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 cbdccc1ca420..9e539a06a9a2 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; - - /// - /// 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)); + : base(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 23de94e8247c..000000000000 --- 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 40db38e05303..6edeeddeed99 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 4af60ece4255..ec6d49e7233d 100644 --- a/src/Umbraco.Infrastructure/Migrations/MigrationContext.cs +++ b/src/Umbraco.Infrastructure/Migrations/MigrationContext.cs @@ -62,7 +62,7 @@ public void Complete() /// [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 => + where TMigration : AsyncMigrationBase => // 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 3be0a01a4fe0..df8e85a5628d 100644 --- a/src/Umbraco.Infrastructure/Migrations/MigrationPlan.cs +++ b/src/Umbraco.Infrastructure/Migrations/MigrationPlan.cs @@ -115,9 +115,9 @@ private MigrationPlan Add(string? sourceState, string targetState, Type? migrati 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 MigrationPlan To(Guid targetState) /// 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 MigrationPlan From(string? sourceState) /// /// 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 MigrationPlan ToWithReplace(string reco /// 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 bf2d73430541..b3cdadf4f442 100644 --- a/src/Umbraco.Infrastructure/Migrations/MigrationPlanExecutor.cs +++ b/src/Umbraco.Infrastructure/Migrations/MigrationPlanExecutor.cs @@ -117,8 +117,6 @@ public MigrationPlanExecutor( { } - public string Execute(MigrationPlan plan, string fromState) => ExecutePlan(plan, fromState).FinalState; - /// /// Executes the plan. /// @@ -129,13 +127,13 @@ public MigrationPlanExecutor( /// 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) + public async Task ExecutePlanAsync(MigrationPlan plan, string fromState) { plan.Validate(); - ExecutedMigrationPlan result = RunMigrationPlan(plan, fromState); + ExecutedMigrationPlan result = await RunMigrationPlanAsync(plan, fromState).ConfigureAwait(false); - HandlePostMigrations(result); + await HandlePostMigrationsAsync(result).ConfigureAwait(false); // If any completed migration requires us to rebuild cache we'll do that. if (_rebuildCache) @@ -147,14 +145,14 @@ public ExecutedMigrationPlan ExecutePlan(MigrationPlan plan, string fromState) // 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) + private async Task HandlePostMigrationsAsync(ExecutedMigrationPlan result) { // prepare and de-duplicate post-migrations, only keeping the 1st occurence var executedTypes = new HashSet(); @@ -171,8 +169,8 @@ private void HandlePostMigrations(ExecutedMigrationPlan result) } _logger.LogInformation("PostMigration: {migrationContextFullName}.", migrationContextPostMigration.FullName); - MigrationBase postMigration = _migrationBuilder.Build(migrationContextPostMigration, executedMigrationContext); - postMigration.Run(); + AsyncMigrationBase postMigration = _migrationBuilder.Build(migrationContextPostMigration, executedMigrationContext); + await postMigration.RunAsync().ConfigureAwait(false); executedTypes.Add(migrationContextPostMigration); } @@ -180,7 +178,7 @@ private void HandlePostMigrations(ExecutedMigrationPlan result) } } - private ExecutedMigrationPlan RunMigrationPlan(MigrationPlan plan, string fromState) + private async Task RunMigrationPlanAsync(MigrationPlan plan, string fromState) { _logger.LogInformation("Starting '{MigrationName}'...", plan.Name); var nextState = fromState; @@ -201,13 +199,13 @@ private ExecutedMigrationPlan RunMigrationPlan(MigrationPlan plan, string fromSt 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) @@ -227,7 +225,6 @@ private ExecutedMigrationPlan RunMigrationPlan(MigrationPlan plan, string fromSt }; } - IEnumerable nonCompletedMigrationsContexts = executedMigrationContexts.Where(x => x.IsCompleted is false); if (nonCompletedMigrationsContexts.Any()) { @@ -283,12 +280,12 @@ private ExecutedMigrationPlan RunMigrationPlan(MigrationPlan plan, string fromSt }; } - 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; } @@ -298,7 +295,7 @@ private void OnComplete(MigrationPlan plan, string targetState) _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 @@ -313,7 +310,7 @@ private MigrationContext RunScopedMigration(MigrationPlan.Transition transition, _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(); @@ -324,10 +321,10 @@ private MigrationContext RunScopedMigration(MigrationPlan.Transition transition, } } - 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 9ce64977c0d2..bc94591dbbd0 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/UnscopedAsyncMigrationBase.cs b/src/Umbraco.Infrastructure/Migrations/UnscopedAsyncMigrationBase.cs new file mode 100644 index 000000000000..0b5ced41a4c4 --- /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 910b9753de27..a182d2237021 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 b73967f400b0..e449dc3770b7 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/Upgrader.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/Upgrader.cs @@ -36,7 +36,7 @@ public class Upgrader /// /// A scope provider. /// A key-value service. - public ExecutedMigrationPlan Execute( + public async Task ExecuteAsync( IMigrationPlanExecutor migrationPlanExecutor, ICoreScopeProvider scopeProvider, IKeyValueService keyValueService) @@ -53,7 +53,7 @@ public ExecutedMigrationPlan Execute( 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 +86,4 @@ private string GetInitialState(ICoreScopeProvider scopeProvider, IKeyValueServic 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 000000000000..e9bb8df92fc2 --- /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 d8becf0bfacd..59a921364f16 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 @@ protected AutomaticPackageMigrationPlan(string packageName) /// 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 @@ protected AutomaticPackageMigrationPlan(string packageName, string planName) /// 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 @@ protected sealed override void DefinePlan() /// /// 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 MigrateToPackageData( 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 f826dd9dfe05..8265fa5d14ae 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 8b28628e4cd9..e557181a517e 100644 --- a/src/Umbraco.Infrastructure/Packaging/ImportPackageBuilder.cs +++ b/src/Umbraco.Infrastructure/Packaging/ImportPackageBuilder.cs @@ -38,7 +38,7 @@ public ImportPackageBuilder( public void Do() => 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 6f0355f6741d..4d5d996edd52 100644 --- a/src/Umbraco.Infrastructure/Packaging/PackageMigrationBase.cs +++ b/src/Umbraco.Infrastructure/Packaging/PackageMigrationBase.cs @@ -8,17 +8,11 @@ namespace Umbraco.Cms.Infrastructure.Packaging; -public abstract class PackageMigrationBase : MigrationBase +/// +[Obsolete("Use AsyncPackageMigrationBase instead. This class will be removed in a future version.")] +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 PackageMigrationBase( IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, IMigrationContext context, IOptions packageMigrationsSettings) - : base(context) - { - _packagingService = packagingService; - _mediaService = mediaService; - _mediaFileManager = mediaFileManager; - _mediaUrlGenerators = mediaUrlGenerators; - _shortStringHelper = shortStringHelper; - _contentTypeBaseServiceProvider = contentTypeBaseServiceProvider; - _packageMigrationsSettings = packageMigrationsSettings; - } + : base(packagingService, mediaService, mediaFileManager, mediaUrlGenerators, shortStringHelper, contentTypeBaseServiceProvider, context, packageMigrationsSettings) + { } - public IImportPackageBuilder ImportPackage => BeginBuild( - new ImportPackageBuilder( - _packagingService, - _mediaService, - _mediaFileManager, - _mediaUrlGenerators, - _shortStringHelper, - _contentTypeBaseServiceProvider, - Context, - _packageMigrationsSettings)); + /// + protected override Task MigrateAsync() + { + Migrate(); + return Task.CompletedTask; + } + /// + /// 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 cd18e58563db..09408824a39b 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Core/RuntimeStateTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/RuntimeStateTests.cs @@ -79,7 +79,7 @@ public TestMigrationPlan() : base(TestMigrationPlanName) protected override void DefinePlan() => To(TestMigrationFinalState); } - private class TestMigration : PackageMigrationBase + private sealed class TestMigration : AsyncPackageMigrationBase { public TestMigration( IPackagingService packagingService, @@ -88,11 +88,16 @@ public TestMigration( MediaUrlGeneratorCollection mediaUrlGenerators, IShortStringHelper shortStringHelper, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, - IMigrationContext context, IOptions options) + IMigrationContext context, + IOptions options) : base(packagingService, mediaService, mediaFileManager, mediaUrlGenerators, shortStringHelper, contentTypeBaseServiceProvider, context, options) + { } + + protected override Task MigrateAsync() { - } + ImportPackage.FromEmbeddedResource().Do(); - protected override void Migrate() => 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 cd138283ebf1..e3bc9c39bf9c 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Migrations/AdvancedMigrationTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Migrations/AdvancedMigrationTests.cs @@ -50,7 +50,7 @@ public class AdvancedMigrationTests : UmbracoIntegrationTest ServiceScopeFactory); [Test] - public void CreateTableOfTDto() + public async Task CreateTableOfTDtoAsync() { var builder = Mock.Of(); Mock.Get(builder) @@ -72,7 +72,7 @@ public void CreateTableOfTDto() .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 +82,7 @@ public void CreateTableOfTDto() } [Test] - public void DeleteKeysAndIndexesOfTDto() + public async Task DeleteKeysAndIndexesOfTDtoAsync() { var builder = Mock.Of(); Mock.Get(builder) @@ -108,13 +108,13 @@ public void DeleteKeysAndIndexesOfTDto() .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 +150,13 @@ public void CreateKeysAndIndexesOfTDto() .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 +192,13 @@ public void CreateKeysAndIndexes() .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 +224,7 @@ public void AddColumn() .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 1fb0e784176c..dc0d4b0a4d2f 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 @@ protected override void ConfigureTestServices(IServiceCollection services) => services.AddNotificationHandler(); [Test] - public void CanRerunPartiallyCompletedMigration() + public async Task CanRerunPartiallyCompletedMigration() { var plan = new MigrationPlan("test") .From(string.Empty) @@ -52,7 +52,7 @@ public void CanRerunPartiallyCompletedMigration() 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 void CanRerunPartiallyCompletedMigration() // 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 void CanRerunPartiallyCompletedMigration() } [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 void CanRunMigrationTwice() } [Test] - public void StateIsOnlySavedIfAMigrationSucceeds() + public async Task StateIsOnlySavedIfAMigrationSucceeds() { var plan = new MigrationPlan("test") .From(string.Empty) @@ -121,7 +121,7 @@ public void StateIsOnlySavedIfAMigrationSucceeds() .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 void StateIsOnlySavedIfAMigrationSucceeds() } [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 void ScopesAreCreatedIfNecessary() [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 void UmbracoPlanExecutedNotificationIsAlwaysPublished(bool shouldSucceed) // 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 @@ protected override void Migrate() => Create .Do(); } -internal class AssertScopeUnscopedTestMigration : UnscopedMigrationBase +internal class AssertScopeUnscopedTestMigration : UnscopedAsyncMigrationBase { private readonly IScopeProvider _scopeProvider; private readonly IScopeAccessor _scopeAccessor; @@ -256,7 +256,7 @@ public AssertScopeUnscopedTestMigration( _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 @@ protected override void Migrate() 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 516e7f80c146..e8f79d7da7e7 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 @@ private MigrationContext GetMigrationContext(out TestDatabase db) } [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 void Drop_Foreign_Key() // 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 CreateColumnMigration(IMigrationContext context) } [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 @@ protected override void Migrate() => [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 b70d03622796..0f8dcb4f288b 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; @@ -77,7 +77,10 @@ public void CanExecute() loggerFactory, migrationBuilder, databaseFactory, - Mock.Of(), distributedCache, Mock.Of(), Mock.Of()); + Mock.Of(), + distributedCache, + Mock.Of(), + Mock.Of()); var plan = new MigrationPlan("default") .From(string.Empty) @@ -95,7 +98,7 @@ public void CanExecute() 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 1e4ddd6f348d..63a310c09cda 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 @@ private MigrationContext GetMigrationContext() => 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 void DetectBadMigration1() { var migrationContext = GetMigrationContext(); MigrationBase migration = new BadMigration1(migrationContext); - Assert.Throws(() => migration.Run()); + Assert.ThrowsAsync(migration.RunAsync); } [Test] @@ -97,7 +97,7 @@ public void DetectBadMigration2() { var migrationContext = GetMigrationContext(); MigrationBase migration = new BadMigration2(migrationContext); - Assert.Throws(() => migration.Run()); + Assert.ThrowsAsync(migration.RunAsync); } public class GoodMigration : MigrationBase