/// <summary>
        /// Applies the provided migrations to the target system. The engine does not check
        /// if the specified migrations were already applied.
        /// PLEASE NOTE: this method will not throw. You must check the resulting summary for errors that might have occurred.
        /// </summary>
        /// <param name="pendingMigrations">The list of migrations that should be applied.</param>
        /// <param name="now">
        /// The current time when the migration engine starts to execute (optional). Please use a UTC time stamp if possible. If
        /// you do not provide a value, <see cref="DateTime.UtcNow" /> will be used.
        /// </param>
        /// <param name="cancellationToken">The token to cancel this asynchronous operation (optional).</param>
        /// <returns>A summary of all migrations that have been applied in this run.</returns>
        public virtual async Task <MigrationSummary <TMigrationInfo> > ApplyMigrationsAsync(List <PendingMigration <TMigrationVersion> >?pendingMigrations,
                                                                                            DateTime?now = null,
                                                                                            CancellationToken cancellationToken = default)
        {
            if (pendingMigrations.IsNullOrEmpty())
            {
                return(MigrationSummary <TMigrationInfo> .Empty);
            }

            var timeStamp         = now ?? DateTime.UtcNow;
            var appliedMigrations = new List <TMigrationInfo>(pendingMigrations.Count);

            for (var i = 0; i < pendingMigrations.Count; i++)
            {
                var pendingMigration = pendingMigrations[i];

                IMigrationSession <TMigrationContext, TMigrationInfo>?session = null;
                TMigration?migration = default;
                try
                {
                    migration = MigrationFactory.CreateMigration(pendingMigration.MigrationType);
                    session   = await SessionFactory.CreateSessionForMigrationAsync(migration, cancellationToken);

                    await migration.ApplyAsync(session.Context, cancellationToken);

                    var migrationInfo = CreateMigrationInfo(migration, timeStamp);
                    await session.StoreMigrationInfoAsync(migrationInfo, cancellationToken);

                    await session.SaveChangesAsync(cancellationToken);

                    appliedMigrations.Add(migrationInfo);
                }
                catch (Exception exception)
                {
                    var error = new MigrationError <TMigrationVersion>(pendingMigration.MigrationVersion, exception);
                    return(new MigrationSummary <TMigrationInfo>(error, appliedMigrations));
                }
                finally
                {
                    // ReSharper disable SuspiciousTypeConversion.Global -- clients of the library should be allowed to write disposable migrations
                    if (migration is IAsyncDisposable asyncDisposableMigration)
                    {
                        await asyncDisposableMigration.DisposeAsync();
                    }
                    else if (migration is IDisposable disposableMigration)
                    {
                        disposableMigration.Dispose();
                    }
                    // ReSharper restore SuspiciousTypeConversion.Global

                    if (session != null)
                    {
                        await session.DisposeAsync();
                    }
                }
            }

            return(new MigrationSummary <TMigrationInfo>(appliedMigrations));
        }