11using Microsoft . Extensions . DependencyInjection ;
22using Microsoft . Extensions . Logging ;
3+ using Umbraco . Cms . Core ;
34using Umbraco . Cms . Core . Cache ;
45using Umbraco . Cms . Core . DependencyInjection ;
56using Umbraco . Cms . Core . Migrations ;
67using Umbraco . Cms . Core . PublishedCache ;
78using Umbraco . Cms . Core . Scoping ;
9+ using Umbraco . Cms . Core . Services ;
810using Umbraco . Cms . Infrastructure . Persistence ;
911using Umbraco . Cms . Infrastructure . Scoping ;
1012using Umbraco . Extensions ;
@@ -39,6 +41,7 @@ public class MigrationPlanExecutor : IMigrationPlanExecutor
3941 private readonly IMigrationBuilder _migrationBuilder ;
4042 private readonly IUmbracoDatabaseFactory _databaseFactory ;
4143 private readonly IPublishedSnapshotService _publishedSnapshotService ;
44+ private readonly IKeyValueService _keyValueService ;
4245 private readonly DistributedCache _distributedCache ;
4346 private readonly IScopeAccessor _scopeAccessor ;
4447 private readonly ICoreScopeProvider _scopeProvider ;
@@ -51,19 +54,42 @@ public MigrationPlanExecutor(
5154 IMigrationBuilder migrationBuilder ,
5255 IUmbracoDatabaseFactory databaseFactory ,
5356 IPublishedSnapshotService publishedSnapshotService ,
54- DistributedCache distributedCache )
57+ DistributedCache distributedCache ,
58+ IKeyValueService keyValueService )
5559 {
5660 _scopeProvider = scopeProvider ;
5761 _scopeAccessor = scopeAccessor ;
5862 _loggerFactory = loggerFactory ;
5963 _migrationBuilder = migrationBuilder ;
6064 _databaseFactory = databaseFactory ;
6165 _publishedSnapshotService = publishedSnapshotService ;
66+ _keyValueService = keyValueService ;
6267 _distributedCache = distributedCache ;
6368 _logger = _loggerFactory . CreateLogger < MigrationPlanExecutor > ( ) ;
6469 }
6570
66- [ Obsolete ( "Use constructor with 7 parameters" ) ]
71+ [ Obsolete ( "Use non-obsolete constructor. This will be removed in Umbraco 15." ) ]
72+ public MigrationPlanExecutor (
73+ ICoreScopeProvider scopeProvider ,
74+ IScopeAccessor scopeAccessor ,
75+ ILoggerFactory loggerFactory ,
76+ IMigrationBuilder migrationBuilder ,
77+ IUmbracoDatabaseFactory databaseFactory ,
78+ IPublishedSnapshotService publishedSnapshotService ,
79+ DistributedCache distributedCache )
80+ : this (
81+ scopeProvider ,
82+ scopeAccessor ,
83+ loggerFactory ,
84+ migrationBuilder ,
85+ StaticServiceProvider . Instance . GetRequiredService < IUmbracoDatabaseFactory > ( ) ,
86+ StaticServiceProvider . Instance . GetRequiredService < IPublishedSnapshotService > ( ) ,
87+ StaticServiceProvider . Instance . GetRequiredService < DistributedCache > ( ) ,
88+ StaticServiceProvider . Instance . GetRequiredService < IKeyValueService > ( ) )
89+ {
90+ }
91+
92+ [ Obsolete ( "Use non-obsolete constructor. This will be removed in Umbraco 15." ) ]
6793 public MigrationPlanExecutor (
6894 ICoreScopeProvider scopeProvider ,
6995 IScopeAccessor scopeAccessor ,
@@ -76,7 +102,9 @@ public MigrationPlanExecutor(
76102 migrationBuilder ,
77103 StaticServiceProvider . Instance . GetRequiredService < IUmbracoDatabaseFactory > ( ) ,
78104 StaticServiceProvider . Instance . GetRequiredService < IPublishedSnapshotService > ( ) ,
79- StaticServiceProvider . Instance . GetRequiredService < DistributedCache > ( ) )
105+ StaticServiceProvider . Instance . GetRequiredService < DistributedCache > ( ) ,
106+ StaticServiceProvider . Instance . GetRequiredService < IKeyValueService > ( )
107+ )
80108 {
81109 }
82110
@@ -92,7 +120,6 @@ public MigrationPlanExecutor(
92120 /// <para>Each migration in the plan, may or may not run in a scope depending on the type of plan.</para>
93121 /// <para>A plan can complete partially, the changes of each completed migration will be saved.</para>
94122 /// </remarks>
95- [ Obsolete ( "This will return an ExecutedMigrationPlan in V13" ) ]
96123 public ExecutedMigrationPlan ExecutePlan ( MigrationPlan plan , string fromState )
97124 {
98125 plan . Validate ( ) ;
@@ -104,6 +131,7 @@ public ExecutedMigrationPlan ExecutePlan(MigrationPlan plan, string fromState)
104131 // If any completed migration requires us to rebuild cache we'll do that.
105132 if ( _rebuildCache )
106133 {
134+ _logger . LogInformation ( "Starts rebuilding the cache. This can be a long running operation" ) ;
107135 RebuildCache ( ) ;
108136 }
109137
@@ -160,11 +188,11 @@ private ExecutedMigrationPlan RunMigrationPlan(MigrationPlan plan, string fromSt
160188 {
161189 if ( transition . MigrationType . IsAssignableTo ( typeof ( UnscopedMigrationBase ) ) )
162190 {
163- executedMigrationContexts . Add ( RunUnscopedMigration ( transition . MigrationType , plan ) ) ;
191+ executedMigrationContexts . Add ( RunUnscopedMigration ( transition , plan ) ) ;
164192 }
165193 else
166194 {
167- executedMigrationContexts . Add ( RunScopedMigration ( transition . MigrationType , plan ) ) ;
195+ executedMigrationContexts . Add ( RunScopedMigration ( transition , plan ) ) ;
168196 }
169197 }
170198 catch ( Exception exception )
@@ -184,6 +212,13 @@ private ExecutedMigrationPlan RunMigrationPlan(MigrationPlan plan, string fromSt
184212 } ;
185213 }
186214
215+
216+ IEnumerable < IMigrationContext > nonCompletedMigrationsContexts = executedMigrationContexts . Where ( x => x . IsCompleted is false ) ;
217+ if ( nonCompletedMigrationsContexts . Any ( ) )
218+ {
219+ throw new InvalidOperationException ( $ "Migration ({ transition . MigrationType . FullName } ) has been executed without indicated it was completed correctly.") ;
220+ }
221+
187222 // The plan migration (transition), completed, so we'll add this to our list so we can return this at some point.
188223 completedTransitions . Add ( transition ) ;
189224 nextState = transition . TargetState ;
@@ -233,17 +268,22 @@ private ExecutedMigrationPlan RunMigrationPlan(MigrationPlan plan, string fromSt
233268 } ;
234269 }
235270
236- private MigrationContext RunUnscopedMigration ( Type migrationType , MigrationPlan plan )
271+ private MigrationContext RunUnscopedMigration ( MigrationPlan . Transition transition , MigrationPlan plan )
237272 {
238273 using IUmbracoDatabase database = _databaseFactory . CreateDatabase ( ) ;
239- var context = new MigrationContext ( plan , database , _loggerFactory . CreateLogger < MigrationContext > ( ) ) ;
274+ var context = new MigrationContext ( plan , database , _loggerFactory . CreateLogger < MigrationContext > ( ) , ( ) => OnComplete ( plan , transition . TargetState ) ) ;
240275
241- RunMigration ( migrationType , context ) ;
276+ RunMigration ( transition . MigrationType , context ) ;
242277
243278 return context ;
244279 }
245280
246- private MigrationContext RunScopedMigration ( Type migrationType , MigrationPlan plan )
281+ private void OnComplete ( MigrationPlan plan , string targetState )
282+ {
283+ _keyValueService . SetValue ( Constants . Conventions . Migrations . KeyValuePrefix + plan . Name , targetState ) ;
284+ }
285+
286+ private MigrationContext RunScopedMigration ( MigrationPlan . Transition transition , MigrationPlan plan )
247287 {
248288 // We want to suppress scope (service, etc...) notifications during a migration plan
249289 // execution. This is because if a package that doesn't have their migration plan
@@ -255,9 +295,13 @@ private MigrationContext RunScopedMigration(Type migrationType, MigrationPlan pl
255295 var context = new MigrationContext (
256296 plan ,
257297 _scopeAccessor . AmbientScope ? . Database ,
258- _loggerFactory . CreateLogger < MigrationContext > ( ) ) ;
298+ _loggerFactory . CreateLogger < MigrationContext > ( ) ,
299+ ( ) => OnComplete ( plan , transition . TargetState ) ) ;
300+
301+ RunMigration ( transition . MigrationType , context ) ;
259302
260- RunMigration ( migrationType , context ) ;
303+ // Ensure we mark the context as complete before the scope completes
304+ context . Complete ( ) ;
261305
262306 scope . Complete ( ) ;
263307
0 commit comments