/// <summary> /// Executes the plan. /// </summary> /// <param name="scope">A scope.</param> /// <param name="fromState">The state to start execution at.</param> /// <param name="migrationBuilder">A migration builder.</param> /// <param name="logger">A logger.</param> /// <returns>The final state.</returns> /// <remarks>The plan executes within the scope, which must then be completed.</remarks> public string Execute(IScope scope, string fromState, IMigrationBuilder migrationBuilder, ILogger logger) { Validate(); if (migrationBuilder == null) { throw new ArgumentNullException(nameof(migrationBuilder)); } if (logger == null) { throw new ArgumentNullException(nameof(logger)); } logger.Info <MigrationPlan>("Starting '{MigrationName}'...", Name); var origState = fromState ?? string.Empty; logger.Info <MigrationPlan>("At {OrigState}", string.IsNullOrWhiteSpace(origState) ? "origin": origState); if (!_transitions.TryGetValue(origState, out var transition)) { throw new Exception($"Unknown state \"{origState}\"."); } var context = new MigrationContext(scope.Database, logger); while (transition != null) { var migration = migrationBuilder.Build(transition.MigrationType, context); migration.Migrate(); var nextState = transition.TargetState; origState = nextState; logger.Info <MigrationPlan>("At {OrigState}", origState); if (!_transitions.TryGetValue(origState, out transition)) { throw new Exception($"Unknown state \"{origState}\"."); } } logger.Info <MigrationPlan>("Done (pending scope completion)."); // safety check if (origState != _finalState) { throw new Exception($"Internal error, reached state {origState} which is not final state {_finalState}"); } return(origState); }
/// <summary> /// Executes the plan. /// </summary> /// <param name="scope">A scope.</param> /// <param name="fromState">The state to start execution at.</param> /// <param name="migrationBuilder">A migration builder.</param> /// <param name="logger">A logger.</param> /// <param name="loggerFactory"></param> /// <returns>The final state.</returns> /// <remarks>The plan executes within the scope, which must then be completed.</remarks> public string Execute(MigrationPlan plan, string fromState) { plan.Validate(); _logger.LogInformation("Starting '{MigrationName}'...", plan.Name); fromState ??= string.Empty; var nextState = fromState; _logger.LogInformation("At {OrigState}", string.IsNullOrWhiteSpace(nextState) ? "origin" : nextState); if (!plan.Transitions.TryGetValue(nextState, out MigrationPlan.Transition? transition)) { plan.ThrowOnUnknownInitialState(nextState); } using (ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true)) { // 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 // executed is listening to service notifications to perform some persistence logic, // that packages notification handlers may explode because that package isn't fully installed yet. using (scope.Notifications.Suppress()) { var context = new MigrationContext(plan, _scopeAccessor.AmbientScope?.Database, _loggerFactory.CreateLogger <MigrationContext>()); while (transition != null) { _logger.LogInformation("Execute {MigrationType}", transition.MigrationType.Name); var migration = _migrationBuilder.Build(transition.MigrationType, context); migration.Run(); nextState = transition.TargetState; _logger.LogInformation("At {OrigState}", nextState); // throw a raw exception here: this should never happen as the plan has // been validated - this is just a paranoid safety test if (!plan.Transitions.TryGetValue(nextState, out transition)) { throw new InvalidOperationException($"Unknown state \"{nextState}\"."); } } // prepare and de-duplicate post-migrations, only keeping the 1st occurence var temp = new HashSet <Type>(); var postMigrationTypes = context.PostMigrations .Where(x => !temp.Contains(x)) .Select(x => { temp.Add(x); return(x); }); // run post-migrations foreach (var postMigrationType in postMigrationTypes) { _logger.LogInformation($"PostMigration: {postMigrationType.FullName}."); var postMigration = _migrationBuilder.Build(postMigrationType, context); postMigration.Run(); } } } _logger.LogInformation("Done (pending scope completion)."); // safety check - again, this should never happen as the plan has been validated, // and this is just a paranoid safety test var finalState = plan.FinalState; if (nextState != finalState) { throw new InvalidOperationException($"Internal error, reached state {nextState} which is not final state {finalState}"); } return(nextState); }
/// <summary> /// Executes the plan. /// </summary> /// <param name="scope">A scope.</param> /// <param name="fromState">The state to start execution at.</param> /// <param name="migrationBuilder">A migration builder.</param> /// <param name="logger">A logger.</param> /// <returns>The final state.</returns> /// <remarks>The plan executes within the scope, which must then be completed.</remarks> public string Execute(IScope scope, string fromState, IMigrationBuilder migrationBuilder, ILogger logger) { Validate(); if (migrationBuilder == null) { throw new ArgumentNullException(nameof(migrationBuilder)); } if (logger == null) { throw new ArgumentNullException(nameof(logger)); } logger.Info <MigrationPlan>("Starting '{MigrationName}'...", Name); var origState = fromState ?? string.Empty; logger.Info <MigrationPlan>("At {OrigState}", string.IsNullOrWhiteSpace(origState) ? "origin": origState); if (!_transitions.TryGetValue(origState, out var transition)) { ThrowOnUnknownInitialState(origState); } var context = new MigrationContext(scope.Database, logger); context.PostMigrations.AddRange(_postMigrationTypes); while (transition != null) { logger.Info <MigrationPlan>("Execute {MigrationType}", transition.MigrationType.Name); var migration = migrationBuilder.Build(transition.MigrationType, context); migration.Migrate(); var nextState = transition.TargetState; origState = nextState; logger.Info <MigrationPlan>("At {OrigState}", origState); // throw a raw exception here: this should never happen as the plan has // been validated - this is just a paranoid safety test if (!_transitions.TryGetValue(origState, out transition)) { throw new Exception($"Unknown state \"{origState}\"."); } } // prepare and de-duplicate post-migrations, only keeping the 1st occurence var temp = new HashSet <Type>(); var postMigrationTypes = PreparePostMigrations(context.PostMigrations) .Where(x => !temp.Contains(x)) .Select(x => { temp.Add(x); return(x); }); // run post-migrations foreach (var postMigrationType in postMigrationTypes) { logger.Info <MigrationPlan>($"PostMigration: {postMigrationType.FullName}."); var postMigration = migrationBuilder.Build(postMigrationType, context); postMigration.Migrate(); } logger.Info <MigrationPlan>("Done (pending scope completion)."); // safety check - again, this should never happen as the plan has been validated, // and this is just a paranoid safety test if (origState != _finalState) { throw new Exception($"Internal error, reached state {origState} which is not final state {_finalState}"); } return(origState); }