Something went wrong. Try again.
A database migration tool for .NET
Something went wrong. Try again.
5.0 kB · 119 lines
C#
at develop
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120using System;using System.Collections.Generic;using System.Data;using System.Linq;using Microsoft.Extensions.Logging;
namespace Migrato.Core;
/// <summary>/// Orchestrates applying and reverting migrations against a database, via an <see cref="IMigrationProvider"/>/// </summary>public partial class MigrationRunner(IMigrationProvider provider, ILogger<MigrationRunner> logger){ /// <summary> /// Reports the applied/pending status of each of <paramref name="migrations"/> /// </summary> public IReadOnlyList<MigrationStatusEntry> GetStatus(IDbConnection connection, IReadOnlyList<Migration> migrations) { provider.EnsureVersionTable(connection); var applied = provider.GetAppliedMigrations(connection).ToDictionary(a => a.Version);
return migrations .OrderBy(m => m.Version) .Select(m => applied.TryGetValue(m.Version, out var appliedMigration) ? new MigrationStatusEntry { Version = m.Version, Name = m.Name, IsApplied = true, AppliedAt = appliedMigration.AppliedAt } : new MigrationStatusEntry { Version = m.Version, Name = m.Name, IsApplied = false }) .ToList(); }
/// <summary> /// Applies every migration in <paramref name="migrations"/> that has not already been applied, in version order. /// Stops and rethrows on the first failure; migrations applied before the failure remain applied. /// </summary> /// <returns>The migrations that were applied by this call</returns> public IReadOnlyList<Migration> MigrateUp(IDbConnection connection, IReadOnlyList<Migration> migrations) { LogCheckingVersionTableOnConnection(connection.ConnectionString); provider.EnsureVersionTable(connection); var appliedVersions = provider.GetAppliedMigrations(connection).Select(a => a.Version).ToHashSet();
var applied = new List<Migration>(); foreach (var migration in migrations.OrderBy(m => m.Version)) { if (appliedVersions.Contains(migration.Version)) { continue; } LogRunningUpMigration($"{migration.Version}_{migration.Name}"); RunInTransaction(connection, migration.UpSql, transaction => provider.RecordMigration(connection, transaction, migration)); applied.Add(migration); LogFinishedUpMigration($"{migration.Version}_{migration.Name}"); }
return applied; }
/// <summary> /// Reverts the highest-versioned applied migration in <paramref name="migrations"/> /// </summary> /// <returns>The migration that was reverted, or null if none of <paramref name="migrations"/> were applied</returns> public Migration? MigrateDown(IDbConnection connection, IReadOnlyList<Migration> migrations) { LogCheckingVersionTableOnConnection(connection.ConnectionString); provider.EnsureVersionTable(connection); var appliedVersions = provider.GetAppliedMigrations(connection).Select(a => a.Version).ToHashSet();
var target = migrations .Where(m => appliedVersions.Contains(m.Version)) .OrderByDescending(m => m.Version) .FirstOrDefault();
if (target is null) { return null; }
LogRunningDownMigrationMigration($"{target.Version}_{target.Name}"); RunInTransaction(connection, target.DownSql, transaction => provider.RemoveMigration(connection, transaction, target.Version)); LogFinishedDownMigrationMigration($"{target.Version}_{target.Name}"); return target; }
private static void RunInTransaction(IDbConnection connection, string sql, Action<IDbTransaction> recordChange) { using var transaction = connection.BeginTransaction(); try { using (var command = connection.CreateCommand()) { command.Transaction = transaction; command.CommandText = sql; command.ExecuteNonQuery(); }
recordChange(transaction); transaction.Commit(); } catch { transaction.Rollback(); throw; } }
[LoggerMessage(LogLevel.Information, "Running up migration {migration}")] partial void LogRunningUpMigration(string migration);
[LoggerMessage(LogLevel.Debug, "Checking version table on {connection}")] partial void LogCheckingVersionTableOnConnection(string connection);
[LoggerMessage(LogLevel.Information, "Finished up migration {migration}")] partial void LogFinishedUpMigration(string migration);
[LoggerMessage(LogLevel.Information, "running down migration {migration}")] partial void LogRunningDownMigrationMigration(string migration);
[LoggerMessage(LogLevel.Information, "Finished down migration {migration}")] partial void LogFinishedDownMigrationMigration(string migration);}