/// <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);
        }
Example #2
0
        /// <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);
        }
Example #3
0
        /// <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);
        }