├── .gitattributes ├── .gitignore ├── CHANGELOG.md ├── LICENSE.txt ├── README.md ├── Rakefile ├── SimpleMigrations.sln ├── examples ├── SQLiteNet │ ├── App.config │ ├── Migrations │ │ └── 1_CreateUsers.cs │ ├── Program.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── SQLite.cs │ ├── SQLiteAsync.cs │ ├── SQLiteNet.csproj │ ├── SQLiteNetDatabaseProvider.cs │ ├── SQLiteNetMigration.cs │ ├── SchemaVersion.cs │ ├── packages.config │ └── sqlite3.dll ├── SeparateMigrator │ ├── App.config │ ├── Migrations │ │ └── 1_CreateUsers.cs │ ├── Program.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── SeparateMigrator.csproj │ └── packages.config └── SimpleMigrations.Examples.sln ├── icon.png ├── icon.svg ├── nuget.config └── src ├── Simple.Migrations.IntegrationTests ├── ConnectionStrings.cs ├── CustomMigrationProvider.cs ├── IMigrationStringsProvider.cs ├── Migrations │ └── 1_CreateUsersTable.cs ├── Mssql │ ├── MssqlStringsProvider.cs │ └── MssqlTests.cs ├── Mysql │ ├── MysqlStringsProvider.cs │ └── MysqlTests.cs ├── NUnitLogger.cs ├── Postgresql │ ├── PostgresqlStringsProvider.cs │ └── PostgresqlTests.cs ├── Simple.Migrations.IntegrationTests.csproj ├── Sqlite │ ├── SqliteStringsProvider.cs │ └── SqliteTests.cs └── TestsBase.cs ├── Simple.Migrations.UnitTests ├── DatabaseProviderBaseTests.cs ├── MigrationTests.cs ├── MockDatabaseProvider.cs ├── Simple.Migrations.UnitTests.csproj ├── SimpleMigratorLoadTests.cs └── SimpleMigratorMigrateTests.cs └── Simple.Migrations ├── AssemblyMigrationProvider.cs ├── Console ├── ConsoleLogger.cs ├── ConsoleRunner.cs ├── HelpNeededException.cs └── SubCommand.cs ├── DatabaseProvider ├── DatabaseProviderBase.cs ├── DatabaseProviderBaseWithAdvisoryLock.cs ├── DatabaseProviderBaseWithVersionTableLock.cs ├── MssqlDatabaseProvider.cs ├── MysqlDatabaseProvider.cs ├── PostgresqlDatabaseProvider.cs └── SqliteDatabaseProvider.cs ├── IDatabaseProvider.cs ├── ILogger.cs ├── IMigration.cs ├── IMigrationLogger.cs ├── IMigrationProvider.cs ├── ISimpleMigrator.cs ├── Migration.cs ├── MigrationAttribute.cs ├── MigrationData.cs ├── MigrationDirection.cs ├── MigrationException.cs ├── MigrationLoadFailedException.cs ├── MigrationNotFoundException.cs ├── MigrationRunData.cs ├── MissingMigrationException.cs ├── NullLogger.cs ├── Simple.Migrations.csproj ├── SimpleMigrations.ruleset ├── SimpleMigrator.cs └── SimpleMigrator`2.cs /.gitattributes: -------------------------------------------------------------------------------- 1 | *.cs eol=crlf 2 | *.xaml eol=crlf 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #Visual Studio files 2 | *.[Oo]bj 3 | *.user 4 | *.aps 5 | *.pch 6 | *.vspscc 7 | *.vssscc 8 | *_i.c 9 | *_p.c 10 | *.ncb 11 | *.suo 12 | *.tlb 13 | *.tlh 14 | *.bak 15 | *.[Cc]ache 16 | *.ilk 17 | *.log 18 | *.lib 19 | *.sbr 20 | *.sdf 21 | *.opensdf 22 | *.unsuccessfulbuild 23 | ipch/ 24 | obj/ 25 | [Bb]in 26 | [Dd]ebug*/ 27 | [Rr]elease*/ 28 | 29 | #Project files 30 | [Bb]uild/ 31 | 32 | #NuGet 33 | packages/ 34 | *.nupkg 35 | 36 | #Installer 37 | installer/Output 38 | 39 | *.gjq 40 | *.tmp 41 | Coverage 42 | .vs 43 | TestResult.xml 44 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | 4 | v0.9.21 5 | ------- 6 | 7 | - Add netstandard2.0 target 8 | 9 | v0.9.20 10 | ------- 11 | 12 | - ConsoleRunner returns a suitable exit status (#33) 13 | 14 | v0.9.19 15 | ------- 16 | 17 | - Fix bug where 'to' MigrationData passed to ILogger.EndSequence and ILogger.EndSequenceWithError was incorrect (#31) 18 | 19 | v0.9.18 20 | ------- 21 | 22 | - Fix crash in ConsoleLogger when running on .NET Core without a console 23 | 24 | v0.9.17 25 | ------- 26 | 27 | - Fix the mechanism by which schema creation can be disabled (#27) 28 | 29 | v0.9.16 30 | ------- 31 | 32 | - Allow schema creation to be disabled (by setting the SchemaName property of a DatabaseProvider to null) (#24) 33 | 34 | v0.9.15 35 | ------- 36 | 37 | - Handle loading migrations if all types couldn't be loaded 38 | 39 | v0.9.13, 0.9.14 40 | --------------- 41 | 42 | - No changes - released needed by the CI system 43 | 44 | v0.9.12 45 | ------- 46 | 47 | - Don't log confusing info if there are no migrations to run 48 | - ConsoleRunner's reapply command doesn't assume that migrations are numbered sequentially 49 | - ConsoleLogger doesn't crash if there's no console window available (#20) 50 | - ConsoleRunner displays a stack trace for unexpected errors (#20) 51 | 52 | v0.9.11 53 | ------- 54 | 55 | - Correctly call Logger.EndSequence and Logger.EndSequenceWithError 56 | - Fix the SQLiteNet example, and the README section which documents it 57 | - ConsoleRunner shows a nicer error if the requested migration version was not found 58 | 59 | v0.9.10 60 | ------- 61 | 62 | **BREAKING CHANGE**: Change access modifiers of Migration.Up() and Migration.Down() to protected. You will need to modify your migrations! 63 | 64 | - Database providers ensure schema is created, if necessary 65 | - Add SchemaName property to MssqlDatabaseProvider and PostgresqlDatabaseProvider 66 | - GetSetVersionSql() has access to @OldVersion 67 | - Remove erroneous dependency on Microsoft.Data.SqlClient 68 | 69 | v0.9.9 70 | ------ 71 | 72 | - Remove .NET 4.0 support: it hasn't been requested, isn't supported by Microsoft any more, and might get in the way of possible future async support. 73 | - Allow the command timeout used in migratinos to be configured (#14). 74 | - Throw a specific exception if migration data is missing (#15). 75 | - SimpleMigrator.Load() will reload the current version if called multiple times. 76 | 77 | v0.9.8 78 | ------ 79 | 80 | **BREAKING CHANGE**: The MssqlDatabaseProvider now uses an advisory lock, so the way you instantiate it has changed from: 81 | 82 | ```csharp 83 | var databaseProvider = new MssqlDatabaseProvider(() => new SqlConnection("Connection String")); 84 | var migrator = new SimpleMigrator(migrationsAssembly, databaseProvider); 85 | migrator.Load(); 86 | ``` 87 | 88 | To this: 89 | 90 | ```csharp 91 | using (var connection = new SqlConnection("Connection String")) 92 | { 93 | var databaseProvider = new MssqlDatabaseProvider(connection); 94 | var migrator = new SimpleMigrator(migrationsAssembly, databaseProvider); 95 | migrator.Load(); 96 | } 97 | ``` 98 | 99 | Also add support for .NET 4.0. 100 | 101 | v0.9.7 102 | ------ 103 | 104 | **BREAKING CHANGE**: The architecture has been altered reasonably significantly to allow support for concurrent migrators (#7). 105 | 106 | In particular: 107 | 108 | - `IConnectionProvider` has gone. 109 | - `IVersionProvider` has been renamed to `IDatabaseProvider`, and has taken on additional responsibilities: it now owns the database connection (or database connection factory), and can do locking on the database. 110 | - `Migration` has gone. 111 | - `Migration` has taken on responsibility for opening a transaction if necessary. If you have a migration that must run outside of a transaction, this is no longer configured in the `[Migration]` attribute: instead set the `UseTransaction` property in the migration itself. 112 | 113 | The way in which you instantiate a `SimpleMigrator` has changed. Instead of doing this: 114 | 115 | ```csharp 116 | using SimpleMigrations; 117 | using SimpleMigrations.VersionProviders; 118 | 119 | var migrationsAssembly = Assembly.GetEntryAssembly(); 120 | using (var connection = new ConnectionProvider(/* connection string */)) 121 | { 122 | var versionProvider = new SQLiteVersionProvider(); 123 | 124 | var migrator = new SimpleMigrator(migrationsAssembly, connection, versionProvider); 125 | migrator.Load(); 126 | } 127 | ``` 128 | 129 | You now do this: 130 | 131 | ```csharp 132 | using SimpleMigrations; 133 | using SimpleMigrations.DatabaseProvider; 134 | 135 | var migrationsAssembly = Assembly.GetEntryAssembly(); 136 | using (var connection = new ConnectionProvider(/* connection string */)) 137 | { 138 | var databaseProvider = new SQLiteVersionProvider(connection); 139 | 140 | var migrator = new SimpleMigrator(migrationsAssembly, databaseProvider); 141 | migrator.Load(); 142 | } 143 | ``` 144 | 145 | See the README for more details. 146 | 147 | If you have written your own `IVersionProvider`, this will have to be updated. 148 | Instead of deriving from `VersionProviderBase`, you should derive from either `DatabaseProviderBaseWithAdvisoryLock` or `DatabaseProviderBaseWithVersionTableLock`, depending on whether you want to use an advisory lock or lock on the version table to prevent concurrent migrators, see the README for more details. 149 | 150 | If you do not want to worry about concurrent migrators, then derive from `DatabaseProviderBaseWithAdvisoryLock` and override `AcquireAdvisoryLock` and `ReleaseAdvisoryLock` to do nothing, see `SqliteDatabaseProvider` for an example. 151 | 152 | 153 | 154 | 155 | v0.9.6 156 | ------ 157 | 158 | - No code change: version bump required for build server 159 | 160 | v0.9.5 161 | ------ 162 | 163 | - Change nuget package title to 'Simple.Migrations', so NuGet will let us publish again 164 | 165 | v0.9.4 166 | ------ 167 | 168 | - Add support for .NET Core (.NET Standard 1.2) 169 | - Add MSSQL and MySQL version providers 170 | - Allow migration discovery to be customised, using `IMigrationProvider` 171 | - Fix use of transactions with MSSQL 172 | - General robustness improvements 173 | 174 | v0.9.3 175 | ------ 176 | 177 | - No code change: version bump required for build server 178 | 179 | v0.9.2 180 | ------ 181 | 182 | - Change NuGet ID to Simple.migrations to avoid conflict 183 | 184 | v0.9.1 185 | ------ 186 | 187 | - Add documentation and samples 188 | - Create NuGet package 189 | 190 | v0.9.0 191 | ------ 192 | 193 | - Initial release -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Antony Male 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'json' 2 | 3 | SIMPLEMIGRATIONS_DIR = 'src/Simple.Migrations' 4 | UNIT_TESTS_DIR = 'src/Simple.Migrations.UnitTests' 5 | 6 | ASSEMBLY_INFO = File.join(SIMPLEMIGRATIONS_DIR, 'Properties/AssemblyInfo.cs') 7 | SIMPLEMIGRATIONS_JSON = File.join(SIMPLEMIGRATIONS_DIR, 'project.json') 8 | 9 | SIMPLEMIGRATIONS_CSPROJ = File.join(SIMPLEMIGRATIONS_DIR, 'Simple.Migrations.csproj') 10 | NUGET_DIR = File.join(File.dirname(__FILE__), 'NuGet') 11 | 12 | desc "Create NuGet package" 13 | task :package do 14 | sh 'dotnet', 'pack', '--no-build', '--configuration=Release', "--output=\"#{NUGET_DIR}\"", '--include-symbols', SIMPLEMIGRATIONS_DIR 15 | end 16 | 17 | desc "Bump version number" 18 | task :version, [:version] do |t, args| 19 | version = args[:version] 20 | 21 | content = IO.read(SIMPLEMIGRATIONS_CSPROJ) 22 | content[/(.+?)<\/VersionPrefix>/, 1] = version 23 | File.open(SIMPLEMIGRATIONS_CSPROJ, 'w'){ |f| f.write(content) } 24 | end 25 | 26 | desc "Build the project for release" 27 | task :build do 28 | sh 'dotnet', 'build', '--configuration=Release', SIMPLEMIGRATIONS_DIR 29 | end 30 | 31 | desc "Run tests" 32 | task :test do 33 | Dir.chdir(UNIT_TESTS_DIR) do 34 | sh 'dotnet', 'test' 35 | end 36 | end -------------------------------------------------------------------------------- /SimpleMigrations.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26127.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{D9FF6CA2-297E-4C3C-BB03-9B70313E52D3}" 7 | ProjectSection(SolutionItems) = preProject 8 | global.json = global.json 9 | EndProjectSection 10 | EndProject 11 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Simple.Migrations", "src\Simple.Migrations\Simple.Migrations.csproj", "{D9EBA58B-647F-4196-AA67-0EE9CABC16D4}" 12 | EndProject 13 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Simple.Migrations.IntegrationTests", "src\Simple.Migrations.IntegrationTests\Simple.Migrations.IntegrationTests.csproj", "{801BE904-9ADE-4F8A-8F2C-CB3517C63B05}" 14 | EndProject 15 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Simple.Migrations.UnitTests", "src\Simple.Migrations.UnitTests\Simple.Migrations.UnitTests.csproj", "{E255C6ED-F507-4E92-8420-FB135B729EBF}" 16 | EndProject 17 | Global 18 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 19 | Debug|Any CPU = Debug|Any CPU 20 | Release|Any CPU = Release|Any CPU 21 | EndGlobalSection 22 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 23 | {D9EBA58B-647F-4196-AA67-0EE9CABC16D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 24 | {D9EBA58B-647F-4196-AA67-0EE9CABC16D4}.Debug|Any CPU.Build.0 = Debug|Any CPU 25 | {D9EBA58B-647F-4196-AA67-0EE9CABC16D4}.Release|Any CPU.ActiveCfg = Release|Any CPU 26 | {D9EBA58B-647F-4196-AA67-0EE9CABC16D4}.Release|Any CPU.Build.0 = Release|Any CPU 27 | {801BE904-9ADE-4F8A-8F2C-CB3517C63B05}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 28 | {801BE904-9ADE-4F8A-8F2C-CB3517C63B05}.Debug|Any CPU.Build.0 = Debug|Any CPU 29 | {801BE904-9ADE-4F8A-8F2C-CB3517C63B05}.Release|Any CPU.ActiveCfg = Release|Any CPU 30 | {801BE904-9ADE-4F8A-8F2C-CB3517C63B05}.Release|Any CPU.Build.0 = Release|Any CPU 31 | {E255C6ED-F507-4E92-8420-FB135B729EBF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 32 | {E255C6ED-F507-4E92-8420-FB135B729EBF}.Debug|Any CPU.Build.0 = Debug|Any CPU 33 | {E255C6ED-F507-4E92-8420-FB135B729EBF}.Release|Any CPU.ActiveCfg = Release|Any CPU 34 | {E255C6ED-F507-4E92-8420-FB135B729EBF}.Release|Any CPU.Build.0 = Release|Any CPU 35 | EndGlobalSection 36 | GlobalSection(SolutionProperties) = preSolution 37 | HideSolutionNode = FALSE 38 | EndGlobalSection 39 | EndGlobal 40 | -------------------------------------------------------------------------------- /examples/SQLiteNet/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /examples/SQLiteNet/Migrations/1_CreateUsers.cs: -------------------------------------------------------------------------------- 1 | using SimpleMigrations; 2 | 3 | namespace SQLiteNet.Migrations 4 | { 5 | [Migration(1)] 6 | public class CreateUsers : SQLiteNetMigration 7 | { 8 | public override void Up() 9 | { 10 | Execute(@"CREATE TABLE Users ( 11 | Id SERIAL NOT NULL PRIMARY KEY, 12 | Name TEXT NOT NULL 13 | );"); 14 | } 15 | 16 | public override void Down() 17 | { 18 | Execute("DROP TABLE Users"); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/SQLiteNet/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using SimpleMigrations; 8 | using SimpleMigrations.Console; 9 | using SQLite; 10 | 11 | namespace SQLiteNet 12 | { 13 | class Program 14 | { 15 | static int Main(string[] args) 16 | { 17 | using (var connection = new SQLiteConnection("SQLiteNetdatabase.sqlite")) 18 | { 19 | var databaseProvider = new SQLiteNetDatabaseProvider(connection); 20 | 21 | var migrator = new SimpleMigrator( 22 | Assembly.GetEntryAssembly(), databaseProvider); 23 | var runner = new ConsoleRunner(migrator); 24 | return runner.Run(args); 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /examples/SQLiteNet/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("SQLiteNet")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("SQLiteNet")] 13 | [assembly: AssemblyCopyright("Copyright © 2015")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("1298770c-ff0a-48ea-aaea-1519dde13f1f")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /examples/SQLiteNet/SQLiteAsync.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2012 Krueger Systems, Inc. 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in 12 | // all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | // THE SOFTWARE. 21 | // 22 | 23 | using System; 24 | using System.Collections; 25 | using System.Collections.Generic; 26 | using System.Linq; 27 | using System.Linq.Expressions; 28 | using System.Threading; 29 | using System.Threading.Tasks; 30 | 31 | namespace SQLite 32 | { 33 | public partial class SQLiteAsyncConnection 34 | { 35 | SQLiteConnectionString _connectionString; 36 | SQLiteOpenFlags _openFlags; 37 | 38 | public SQLiteAsyncConnection(string databasePath, bool storeDateTimeAsTicks = false) 39 | : this(databasePath, SQLiteOpenFlags.ReadWrite | SQLiteOpenFlags.Create, storeDateTimeAsTicks) 40 | { 41 | } 42 | 43 | public SQLiteAsyncConnection(string databasePath, SQLiteOpenFlags openFlags, bool storeDateTimeAsTicks = false) 44 | { 45 | _openFlags = openFlags; 46 | _connectionString = new SQLiteConnectionString(databasePath, storeDateTimeAsTicks); 47 | } 48 | 49 | SQLiteConnectionWithLock GetConnection () 50 | { 51 | return SQLiteConnectionPool.Shared.GetConnection (_connectionString, _openFlags); 52 | } 53 | 54 | public Task CreateTableAsync () 55 | where T : new () 56 | { 57 | return CreateTablesAsync (typeof (T)); 58 | } 59 | 60 | public Task CreateTablesAsync () 61 | where T : new () 62 | where T2 : new () 63 | { 64 | return CreateTablesAsync (typeof (T), typeof (T2)); 65 | } 66 | 67 | public Task CreateTablesAsync () 68 | where T : new () 69 | where T2 : new () 70 | where T3 : new () 71 | { 72 | return CreateTablesAsync (typeof (T), typeof (T2), typeof (T3)); 73 | } 74 | 75 | public Task CreateTablesAsync () 76 | where T : new () 77 | where T2 : new () 78 | where T3 : new () 79 | where T4 : new () 80 | { 81 | return CreateTablesAsync (typeof (T), typeof (T2), typeof (T3), typeof (T4)); 82 | } 83 | 84 | public Task CreateTablesAsync () 85 | where T : new () 86 | where T2 : new () 87 | where T3 : new () 88 | where T4 : new () 89 | where T5 : new () 90 | { 91 | return CreateTablesAsync (typeof (T), typeof (T2), typeof (T3), typeof (T4), typeof (T5)); 92 | } 93 | 94 | public Task CreateTablesAsync (params Type[] types) 95 | { 96 | return Task.Factory.StartNew (() => { 97 | CreateTablesResult result = new CreateTablesResult (); 98 | var conn = GetConnection (); 99 | using (conn.Lock ()) { 100 | foreach (Type type in types) { 101 | int aResult = conn.CreateTable (type); 102 | result.Results[type] = aResult; 103 | } 104 | } 105 | return result; 106 | }); 107 | } 108 | 109 | public Task DropTableAsync () 110 | where T : new () 111 | { 112 | return Task.Factory.StartNew (() => { 113 | var conn = GetConnection (); 114 | using (conn.Lock ()) { 115 | return conn.DropTable (); 116 | } 117 | }); 118 | } 119 | 120 | public Task InsertAsync (object item) 121 | { 122 | return Task.Factory.StartNew (() => { 123 | var conn = GetConnection (); 124 | using (conn.Lock ()) { 125 | return conn.Insert (item); 126 | } 127 | }); 128 | } 129 | 130 | public Task UpdateAsync (object item) 131 | { 132 | return Task.Factory.StartNew (() => { 133 | var conn = GetConnection (); 134 | using (conn.Lock ()) { 135 | return conn.Update (item); 136 | } 137 | }); 138 | } 139 | 140 | public Task DeleteAsync (object item) 141 | { 142 | return Task.Factory.StartNew (() => { 143 | var conn = GetConnection (); 144 | using (conn.Lock ()) { 145 | return conn.Delete (item); 146 | } 147 | }); 148 | } 149 | 150 | public Task GetAsync(object pk) 151 | where T : new() 152 | { 153 | return Task.Factory.StartNew(() => 154 | { 155 | var conn = GetConnection(); 156 | using (conn.Lock()) 157 | { 158 | return conn.Get(pk); 159 | } 160 | }); 161 | } 162 | 163 | public Task FindAsync (object pk) 164 | where T : new () 165 | { 166 | return Task.Factory.StartNew (() => { 167 | var conn = GetConnection (); 168 | using (conn.Lock ()) { 169 | return conn.Find (pk); 170 | } 171 | }); 172 | } 173 | 174 | public Task GetAsync (Expression> predicate) 175 | where T : new() 176 | { 177 | return Task.Factory.StartNew(() => 178 | { 179 | var conn = GetConnection(); 180 | using (conn.Lock()) 181 | { 182 | return conn.Get (predicate); 183 | } 184 | }); 185 | } 186 | 187 | public Task FindAsync (Expression> predicate) 188 | where T : new () 189 | { 190 | return Task.Factory.StartNew (() => { 191 | var conn = GetConnection (); 192 | using (conn.Lock ()) { 193 | return conn.Find (predicate); 194 | } 195 | }); 196 | } 197 | 198 | public Task ExecuteAsync (string query, params object[] args) 199 | { 200 | return Task.Factory.StartNew (() => { 201 | var conn = GetConnection (); 202 | using (conn.Lock ()) { 203 | return conn.Execute (query, args); 204 | } 205 | }); 206 | } 207 | 208 | public Task InsertAllAsync (IEnumerable items) 209 | { 210 | return Task.Factory.StartNew (() => { 211 | var conn = GetConnection (); 212 | using (conn.Lock ()) { 213 | return conn.InsertAll (items); 214 | } 215 | }); 216 | } 217 | 218 | public Task UpdateAllAsync (IEnumerable items) 219 | { 220 | return Task.Factory.StartNew (() => { 221 | var conn = GetConnection (); 222 | using (conn.Lock ()) { 223 | return conn.UpdateAll (items); 224 | } 225 | }); 226 | } 227 | 228 | [Obsolete("Will cause a deadlock if any call in action ends up in a different thread. Use RunInTransactionAsync(Action) instead.")] 229 | public Task RunInTransactionAsync (Action action) 230 | { 231 | return Task.Factory.StartNew (() => { 232 | var conn = this.GetConnection (); 233 | using (conn.Lock ()) { 234 | conn.BeginTransaction (); 235 | try { 236 | action (this); 237 | conn.Commit (); 238 | } 239 | catch (Exception) { 240 | conn.Rollback (); 241 | throw; 242 | } 243 | } 244 | }); 245 | } 246 | 247 | public Task RunInTransactionAsync(Action action) 248 | { 249 | return Task.Factory.StartNew(() => 250 | { 251 | var conn = this.GetConnection(); 252 | using (conn.Lock()) 253 | { 254 | conn.BeginTransaction(); 255 | try 256 | { 257 | action(conn); 258 | conn.Commit(); 259 | } 260 | catch (Exception) 261 | { 262 | conn.Rollback(); 263 | throw; 264 | } 265 | } 266 | }); 267 | } 268 | 269 | public AsyncTableQuery Table () 270 | where T : new () 271 | { 272 | // 273 | // This isn't async as the underlying connection doesn't go out to the database 274 | // until the query is performed. The Async methods are on the query iteself. 275 | // 276 | var conn = GetConnection (); 277 | return new AsyncTableQuery (conn.Table ()); 278 | } 279 | 280 | public Task ExecuteScalarAsync (string sql, params object[] args) 281 | { 282 | return Task.Factory.StartNew (() => { 283 | var conn = GetConnection (); 284 | using (conn.Lock ()) { 285 | var command = conn.CreateCommand (sql, args); 286 | return command.ExecuteScalar (); 287 | } 288 | }); 289 | } 290 | 291 | public Task> QueryAsync (string sql, params object[] args) 292 | where T : new () 293 | { 294 | return Task>.Factory.StartNew (() => { 295 | var conn = GetConnection (); 296 | using (conn.Lock ()) { 297 | return conn.Query (sql, args); 298 | } 299 | }); 300 | } 301 | } 302 | 303 | // 304 | // TODO: Bind to AsyncConnection.GetConnection instead so that delayed 305 | // execution can still work after a Pool.Reset. 306 | // 307 | public class AsyncTableQuery 308 | where T : new () 309 | { 310 | TableQuery _innerQuery; 311 | 312 | public AsyncTableQuery (TableQuery innerQuery) 313 | { 314 | _innerQuery = innerQuery; 315 | } 316 | 317 | public AsyncTableQuery Where (Expression> predExpr) 318 | { 319 | return new AsyncTableQuery (_innerQuery.Where (predExpr)); 320 | } 321 | 322 | public AsyncTableQuery Skip (int n) 323 | { 324 | return new AsyncTableQuery (_innerQuery.Skip (n)); 325 | } 326 | 327 | public AsyncTableQuery Take (int n) 328 | { 329 | return new AsyncTableQuery (_innerQuery.Take (n)); 330 | } 331 | 332 | public AsyncTableQuery OrderBy (Expression> orderExpr) 333 | { 334 | return new AsyncTableQuery (_innerQuery.OrderBy (orderExpr)); 335 | } 336 | 337 | public AsyncTableQuery OrderByDescending (Expression> orderExpr) 338 | { 339 | return new AsyncTableQuery (_innerQuery.OrderByDescending (orderExpr)); 340 | } 341 | 342 | public Task> ToListAsync () 343 | { 344 | return Task.Factory.StartNew (() => { 345 | using (((SQLiteConnectionWithLock)_innerQuery.Connection).Lock ()) { 346 | return _innerQuery.ToList (); 347 | } 348 | }); 349 | } 350 | 351 | public Task CountAsync () 352 | { 353 | return Task.Factory.StartNew (() => { 354 | using (((SQLiteConnectionWithLock)_innerQuery.Connection).Lock ()) { 355 | return _innerQuery.Count (); 356 | } 357 | }); 358 | } 359 | 360 | public Task ElementAtAsync (int index) 361 | { 362 | return Task.Factory.StartNew (() => { 363 | using (((SQLiteConnectionWithLock)_innerQuery.Connection).Lock ()) { 364 | return _innerQuery.ElementAt (index); 365 | } 366 | }); 367 | } 368 | 369 | public Task FirstAsync () 370 | { 371 | return Task.Factory.StartNew(() => { 372 | using (((SQLiteConnectionWithLock)_innerQuery.Connection).Lock ()) { 373 | return _innerQuery.First (); 374 | } 375 | }); 376 | } 377 | 378 | public Task FirstOrDefaultAsync () 379 | { 380 | return Task.Factory.StartNew(() => { 381 | using (((SQLiteConnectionWithLock)_innerQuery.Connection).Lock ()) { 382 | return _innerQuery.FirstOrDefault (); 383 | } 384 | }); 385 | } 386 | } 387 | 388 | public class CreateTablesResult 389 | { 390 | public Dictionary Results { get; private set; } 391 | 392 | internal CreateTablesResult () 393 | { 394 | this.Results = new Dictionary (); 395 | } 396 | } 397 | 398 | class SQLiteConnectionPool 399 | { 400 | class Entry 401 | { 402 | public SQLiteConnectionString ConnectionString { get; private set; } 403 | public SQLiteConnectionWithLock Connection { get; private set; } 404 | 405 | public Entry (SQLiteConnectionString connectionString, SQLiteOpenFlags openFlags) 406 | { 407 | ConnectionString = connectionString; 408 | Connection = new SQLiteConnectionWithLock (connectionString, openFlags); 409 | } 410 | 411 | public void OnApplicationSuspended () 412 | { 413 | Connection.Dispose (); 414 | Connection = null; 415 | } 416 | } 417 | 418 | readonly Dictionary _entries = new Dictionary (); 419 | readonly object _entriesLock = new object (); 420 | 421 | static readonly SQLiteConnectionPool _shared = new SQLiteConnectionPool (); 422 | 423 | /// 424 | /// Gets the singleton instance of the connection tool. 425 | /// 426 | public static SQLiteConnectionPool Shared 427 | { 428 | get 429 | { 430 | return _shared; 431 | } 432 | } 433 | 434 | public SQLiteConnectionWithLock GetConnection (SQLiteConnectionString connectionString, SQLiteOpenFlags openFlags) 435 | { 436 | lock (_entriesLock) { 437 | Entry entry; 438 | string key = connectionString.ConnectionString; 439 | 440 | if (!_entries.TryGetValue (key, out entry)) { 441 | entry = new Entry (connectionString, openFlags); 442 | _entries[key] = entry; 443 | } 444 | 445 | return entry.Connection; 446 | } 447 | } 448 | 449 | /// 450 | /// Closes all connections managed by this pool. 451 | /// 452 | public void Reset () 453 | { 454 | lock (_entriesLock) { 455 | foreach (var entry in _entries.Values) { 456 | entry.OnApplicationSuspended (); 457 | } 458 | _entries.Clear (); 459 | } 460 | } 461 | 462 | /// 463 | /// Call this method when the application is suspended. 464 | /// 465 | /// Behaviour here is to close any open connections. 466 | public void ApplicationSuspended () 467 | { 468 | Reset (); 469 | } 470 | } 471 | 472 | class SQLiteConnectionWithLock : SQLiteConnection 473 | { 474 | readonly object _lockPoint = new object (); 475 | 476 | public SQLiteConnectionWithLock (SQLiteConnectionString connectionString, SQLiteOpenFlags openFlags) 477 | : base (connectionString.DatabasePath, openFlags, connectionString.StoreDateTimeAsTicks) 478 | { 479 | } 480 | 481 | public IDisposable Lock () 482 | { 483 | return new LockWrapper (_lockPoint); 484 | } 485 | 486 | private class LockWrapper : IDisposable 487 | { 488 | object _lockPoint; 489 | 490 | public LockWrapper (object lockPoint) 491 | { 492 | _lockPoint = lockPoint; 493 | Monitor.Enter (_lockPoint); 494 | } 495 | 496 | public void Dispose () 497 | { 498 | Monitor.Exit (_lockPoint); 499 | } 500 | } 501 | } 502 | } 503 | 504 | -------------------------------------------------------------------------------- /examples/SQLiteNet/SQLiteNet.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {1298770C-FF0A-48EA-AAEA-1519DDE13F1F} 8 | Exe 9 | Properties 10 | SQLiteNet 11 | SQLiteNet 12 | v4.5.2 13 | 512 14 | true 15 | 16 | 17 | x86 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | x86 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | Always 62 | 63 | 64 | 65 | 66 | {43f0c90b-c696-4bc6-b204-480e4eca43cb} 67 | Simple.Migrations 68 | 69 | 70 | 71 | 78 | -------------------------------------------------------------------------------- /examples/SQLiteNet/SQLiteNetDatabaseProvider.cs: -------------------------------------------------------------------------------- 1 | using SimpleMigrations.DatabaseProvider; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Data.Common; 8 | using SQLite; 9 | using SimpleMigrations; 10 | 11 | namespace SQLiteNet 12 | { 13 | public class SQLiteNetDatabaseProvider : IDatabaseProvider 14 | { 15 | private readonly SQLiteConnection connection; 16 | 17 | public SQLiteNetDatabaseProvider(SQLiteConnection connection) 18 | { 19 | this.connection = connection; 20 | } 21 | 22 | public SQLiteConnection BeginOperation() 23 | { 24 | return this.connection; 25 | } 26 | 27 | public void EndOperation() 28 | { 29 | } 30 | 31 | public long EnsurePrerequisitesCreatedAndGetCurrentVersion() 32 | { 33 | this.connection.CreateTable(); 34 | return this.GetCurrentVersion(); 35 | } 36 | 37 | public long GetCurrentVersion() 38 | { 39 | // Return 0 if the table has no entries 40 | var latestOrNull = this.connection.Table().OrderByDescending(x => x.Id).FirstOrDefault(); 41 | return latestOrNull?.Version ?? 0; 42 | } 43 | 44 | public void UpdateVersion(long oldVersion, long newVersion, string newDescription) 45 | { 46 | this.connection.Insert(new SchemaVersion() 47 | { 48 | Version = newVersion, 49 | AppliedOn = DateTime.Now, 50 | Description = newDescription, 51 | }); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /examples/SQLiteNet/SQLiteNetMigration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using SimpleMigrations; 7 | using SQLite; 8 | 9 | namespace SQLiteNet 10 | { 11 | public abstract class SQLiteNetMigration : IMigration 12 | { 13 | protected bool UseTransaction { get; set; } 14 | 15 | protected SQLiteConnection Connection { get; set; } 16 | 17 | protected IMigrationLogger Logger { get; set; } 18 | 19 | public abstract void Down(); 20 | 21 | public abstract void Up(); 22 | 23 | public void Execute(string sql) 24 | { 25 | this.Logger.LogSql(sql); 26 | this.Connection.Execute(sql); 27 | } 28 | 29 | void IMigration.RunMigration(MigrationRunData data) 30 | { 31 | this.Connection = data.Connection; 32 | this.Logger = data.Logger; 33 | 34 | if (this.UseTransaction) 35 | { 36 | try 37 | { 38 | this.Connection.BeginTransaction(); 39 | 40 | if (data.Direction == MigrationDirection.Up) 41 | this.Up(); 42 | else 43 | this.Down(); 44 | 45 | this.Connection.Commit(); 46 | } 47 | catch 48 | { 49 | this.Connection.Rollback(); 50 | throw; 51 | } 52 | } 53 | else 54 | { 55 | if (data.Direction == MigrationDirection.Up) 56 | this.Up(); 57 | else 58 | this.Down(); 59 | } 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /examples/SQLiteNet/SchemaVersion.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using SQLite; 7 | 8 | namespace SQLiteNet 9 | { 10 | public class SchemaVersion 11 | { 12 | [PrimaryKey, AutoIncrement] 13 | public int Id { get; set; } 14 | 15 | public long Version { get; set; } 16 | 17 | public DateTime AppliedOn { get; set; } 18 | 19 | public string Description { get; set; } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/SQLiteNet/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /examples/SQLiteNet/sqlite3.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canton7/Simple.Migrations/4655dc69a8018efe2afc1f81f0ab91db26e49158/examples/SQLiteNet/sqlite3.dll -------------------------------------------------------------------------------- /examples/SeparateMigrator/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /examples/SeparateMigrator/Migrations/1_CreateUsers.cs: -------------------------------------------------------------------------------- 1 | using SimpleMigrations; 2 | 3 | namespace SeparateMigrator.Migrations 4 | { 5 | [Migration(1)] 6 | public class CreateUsers : Migration 7 | { 8 | protected override void Up() 9 | { 10 | Execute(@"CREATE TABLE Users ( 11 | Id SERIAL NOT NULL PRIMARY KEY, 12 | Name TEXT NOT NULL 13 | );"); 14 | } 15 | 16 | protected override void Down() 17 | { 18 | Execute("DROP TABLE Users"); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/SeparateMigrator/Program.cs: -------------------------------------------------------------------------------- 1 | using System.Data.SQLite; 2 | using System.Reflection; 3 | using SimpleMigrations; 4 | using SimpleMigrations.Console; 5 | using SimpleMigrations.DatabaseProvider; 6 | 7 | namespace SeparateMigrator 8 | { 9 | class Program 10 | { 11 | static int Main(string[] args) 12 | { 13 | var migrationsAssembly = typeof(Program).Assembly; 14 | using (var connection = new SQLiteConnection("DataSource=database.sqlite")) 15 | { 16 | var databaseProvider = new SqliteDatabaseProvider(connection); 17 | 18 | var migrator = new SimpleMigrator(migrationsAssembly, databaseProvider); 19 | 20 | var runner = new ConsoleRunner(migrator); 21 | return runner.Run(args); 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /examples/SeparateMigrator/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("SeparateMigrator")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("SeparateMigrator")] 13 | [assembly: AssemblyCopyright("Copyright © 2015")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("8e3ddc33-1d0f-470a-8f55-9ae3b8234083")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /examples/SeparateMigrator/SeparateMigrator.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {8E3DDC33-1D0F-470A-8F55-9AE3B8234083} 8 | Exe 9 | Properties 10 | SeparateMigrator 11 | SeparateMigrator 12 | v4.5.2 13 | 512 14 | true 15 | 16 | 17 | 18 | 19 | AnyCPU 20 | true 21 | full 22 | false 23 | bin\Debug\ 24 | DEBUG;TRACE 25 | prompt 26 | 4 27 | 28 | 29 | AnyCPU 30 | pdbonly 31 | true 32 | bin\Release\ 33 | TRACE 34 | prompt 35 | 4 36 | 37 | 38 | 39 | 40 | 41 | ..\packages\System.Data.SQLite.Core.1.0.98.1\lib\net451\System.Data.SQLite.dll 42 | True 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | {43f0c90b-c696-4bc6-b204-480e4eca43cb} 63 | Simple.Migrations 64 | 65 | 66 | 67 | 68 | 69 | 70 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 71 | 72 | 73 | 74 | 81 | -------------------------------------------------------------------------------- /examples/SeparateMigrator/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /examples/SimpleMigrations.Examples.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26403.7 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SeparateMigrator", "SeparateMigrator\SeparateMigrator.csproj", "{8E3DDC33-1D0F-470A-8F55-9AE3B8234083}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SQLiteNet", "SQLiteNet\SQLiteNet.csproj", "{1298770C-FF0A-48EA-AAEA-1519DDE13F1F}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Simple.Migrations", "..\src\Simple.Migrations\Simple.Migrations.csproj", "{43F0C90B-C696-4BC6-B204-480E4ECA43CB}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Release|Any CPU = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {8E3DDC33-1D0F-470A-8F55-9AE3B8234083}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {8E3DDC33-1D0F-470A-8F55-9AE3B8234083}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {8E3DDC33-1D0F-470A-8F55-9AE3B8234083}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {8E3DDC33-1D0F-470A-8F55-9AE3B8234083}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {1298770C-FF0A-48EA-AAEA-1519DDE13F1F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {1298770C-FF0A-48EA-AAEA-1519DDE13F1F}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {1298770C-FF0A-48EA-AAEA-1519DDE13F1F}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {1298770C-FF0A-48EA-AAEA-1519DDE13F1F}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {43F0C90B-C696-4BC6-B204-480E4ECA43CB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {43F0C90B-C696-4BC6-B204-480E4ECA43CB}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {43F0C90B-C696-4BC6-B204-480E4ECA43CB}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {43F0C90B-C696-4BC6-B204-480E4ECA43CB}.Release|Any CPU.Build.0 = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | EndGlobal 35 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canton7/Simple.Migrations/4655dc69a8018efe2afc1f81f0ab91db26e49158/icon.png -------------------------------------------------------------------------------- /icon.svg: -------------------------------------------------------------------------------- 1 | 2 | image/svg+xmlS 92 | -------------------------------------------------------------------------------- /nuget.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/Simple.Migrations.IntegrationTests/ConnectionStrings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Npgsql; 6 | 7 | namespace Simple.Migrations.IntegrationTests 8 | { 9 | public static class ConnectionStrings 10 | { 11 | public static readonly string SQLiteDatabase = "database.sqlite"; 12 | public static readonly string SQLite = "DataSource=" + SQLiteDatabase; 13 | public static readonly string MSSQL = @"Server=.;Database=SimpleMigratorTests;Trusted_Connection=True;"; 14 | public static readonly string MySQL = @"Server=localhost;Database=SimpleMigrator;Uid=SimpleMigrator;Pwd=SimpleMigrator;"; 15 | public static readonly string PostgreSQL = @"Server=localhost;Port=5432;Database=SimpleMigrator;User ID=SimpleMigrator;Password=SimpleMigrator"; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Simple.Migrations.IntegrationTests/CustomMigrationProvider.cs: -------------------------------------------------------------------------------- 1 | using SimpleMigrations; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using System.Reflection; 7 | 8 | namespace Simple.Migrations.IntegrationTests 9 | { 10 | public class CustomMigrationProvider : IMigrationProvider 11 | { 12 | public Type[] MigrationTypes { get; set; } = new Type[0]; 13 | 14 | public CustomMigrationProvider() 15 | { 16 | } 17 | 18 | public CustomMigrationProvider(params Type[] migrationTypes) 19 | { 20 | this.MigrationTypes = migrationTypes; 21 | } 22 | 23 | public IEnumerable LoadMigrations() 24 | { 25 | var migrations = from type in this.MigrationTypes 26 | let attribute = type.GetCustomAttribute() 27 | where attribute != null 28 | select new MigrationData(attribute.Version, attribute.Description, type.GetTypeInfo()); 29 | return migrations; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Simple.Migrations.IntegrationTests/IMigrationStringsProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace Simple.Migrations.IntegrationTests 7 | { 8 | public interface IMigrationStringsProvider 9 | { 10 | string CreateUsersTableDown { get; } 11 | string CreateUsersTableUp { get; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Simple.Migrations.IntegrationTests/Migrations/1_CreateUsersTable.cs: -------------------------------------------------------------------------------- 1 | using SimpleMigrations; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | namespace Simple.Migrations.IntegrationTests.Migrations 9 | { 10 | [Migration(1)] 11 | public class CreateUsersTable : Migration 12 | { 13 | private static string upSql; 14 | private static string downSql; 15 | 16 | public static void Setup(IMigrationStringsProvider strings) 17 | { 18 | upSql = strings.CreateUsersTableUp; 19 | downSql = strings.CreateUsersTableDown; 20 | } 21 | 22 | protected override void Up() 23 | { 24 | this.Execute(upSql); 25 | Thread.Sleep(200); 26 | } 27 | 28 | protected override void Down() 29 | { 30 | this.Execute(downSql); 31 | Thread.Sleep(200); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Simple.Migrations.IntegrationTests/Mssql/MssqlStringsProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace Simple.Migrations.IntegrationTests.Mssql 7 | { 8 | public class MssqlStringsProvider : IMigrationStringsProvider 9 | { 10 | public string CreateUsersTableUp => @"CREATE TABLE Users ( 11 | [Id] [int] IDENTITY(1,1) PRIMARY KEY NOT NULL, 12 | Name TEXT NOT NULL 13 | );"; 14 | 15 | public string CreateUsersTableDown => "DROP TABLE Users"; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Simple.Migrations.IntegrationTests/Mssql/MssqlTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using SimpleMigrations; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | using System.Data.SqlClient; 8 | using SimpleMigrations.DatabaseProvider; 9 | using System.Data; 10 | using System.Data.Common; 11 | 12 | namespace Simple.Migrations.IntegrationTests.Mssql 13 | { 14 | [TestFixture] 15 | public class MssqlTests : TestsBase 16 | { 17 | protected override IMigrationStringsProvider MigrationStringsProvider { get; } = new MssqlStringsProvider(); 18 | 19 | protected override bool SupportConcurrentMigrators => true; 20 | 21 | protected override DbConnection CreateConnection() => new SqlConnection(ConnectionStrings.MSSQL); 22 | 23 | protected override IDatabaseProvider CreateDatabaseProvider() => new MssqlDatabaseProvider(this.CreateConnection()); 24 | 25 | protected override void Clean() 26 | { 27 | var connection = this.CreateConnection(); 28 | connection.Open(); 29 | 30 | using (var command = connection.CreateCommand()) 31 | { 32 | command.CommandText = @"EXEC sp_MSforeachtable @command1 = ""DROP TABLE ?"""; 33 | command.ExecuteNonQuery(); 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Simple.Migrations.IntegrationTests/Mysql/MysqlStringsProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace Simple.Migrations.IntegrationTests.Mysql 7 | { 8 | public class MysqlStringsProvider : IMigrationStringsProvider 9 | { 10 | public string CreateUsersTableUp => @"CREATE TABLE Users ( 11 | `Id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT, 12 | `Name` TEXT NOT NULL 13 | );"; 14 | 15 | public string CreateUsersTableDown => "DROP TABLE Users"; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Simple.Migrations.IntegrationTests/Mysql/MysqlTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data.Common; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using MySql.Data.MySqlClient; 7 | using NUnit.Framework; 8 | using SimpleMigrations; 9 | using SimpleMigrations.DatabaseProvider; 10 | 11 | namespace Simple.Migrations.IntegrationTests.Mysql 12 | { 13 | [TestFixture] 14 | public class MysqlTests : TestsBase 15 | { 16 | protected override IMigrationStringsProvider MigrationStringsProvider { get; } = new MysqlStringsProvider(); 17 | 18 | protected override DbConnection CreateConnection() => new MySqlConnection(ConnectionStrings.MySQL); 19 | 20 | protected override IDatabaseProvider CreateDatabaseProvider() => new MysqlDatabaseProvider(this.CreateConnection()); 21 | 22 | protected override bool SupportConcurrentMigrators => true; 23 | 24 | protected override void Clean() 25 | { 26 | using (var connection = this.CreateConnection()) 27 | { 28 | connection.Open(); 29 | 30 | var tables = new List(); 31 | 32 | using (var cmd = connection.CreateCommand()) 33 | { 34 | cmd.CommandText = "SHOW TABLES"; 35 | using (var reader = cmd.ExecuteReader()) 36 | { 37 | while (reader.Read()) 38 | { 39 | tables.Add(reader.GetString(0)); 40 | } 41 | } 42 | } 43 | 44 | foreach (var table in tables) 45 | { 46 | using (var cmd = connection.CreateCommand()) 47 | { 48 | cmd.CommandText = $"DROP TABLE `{table}`"; 49 | cmd.ExecuteNonQuery(); 50 | } 51 | } 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Simple.Migrations.IntegrationTests/NUnitLogger.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using SimpleMigrations; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | 8 | namespace Simple.Migrations.IntegrationTests 9 | { 10 | public class NUnitLogger : ILogger 11 | { 12 | private readonly string name; 13 | 14 | public NUnitLogger(string name) 15 | { 16 | this.name = name; 17 | } 18 | 19 | public bool EnableSqlLogging { get; set; } = true; 20 | 21 | /// 22 | /// Invoked when a sequence of migrations is started 23 | /// 24 | /// Migration being migrated from 25 | /// Migration being migrated to 26 | public void BeginSequence(MigrationData from, MigrationData to) 27 | { 28 | this.WriteHeader($"{this.name}: Migrating from {from.Version}: {from.FullName} to {to.Version}: {to.FullName}"); 29 | } 30 | 31 | /// 32 | /// Invoked when a sequence of migrations is completed successfully 33 | /// 34 | /// Migration which was migrated from 35 | /// Migration which was migrated to 36 | public void EndSequence(MigrationData from, MigrationData to) 37 | { 38 | this.WriteHeader($"{this.name}: Done"); 39 | } 40 | 41 | /// 42 | /// Invoked when a sequence of migrations fails with an error 43 | /// 44 | /// Exception which was encountered 45 | /// Migration which was migrated from 46 | /// Last successful migration which was applied 47 | public void EndSequenceWithError(Exception exception, MigrationData from, MigrationData currentVersion) 48 | { 49 | TestContext.WriteLine($"{this.name}:"); 50 | TestContext.WriteLine($"{this.name}: ERROR: {exception.Message}. Database is currently on version {currentVersion.Version}: {currentVersion.FullName}"); 51 | } 52 | 53 | /// 54 | /// Invoked when an individual migration is started 55 | /// 56 | /// Migration being started 57 | /// Direction of the migration 58 | public void BeginMigration(MigrationData migration, MigrationDirection direction) 59 | { 60 | var term = direction == MigrationDirection.Up ? "migrating" : "reverting"; 61 | this.WriteHeader($"{this.name}: {migration.Version}: {migration.FullName} {term}"); 62 | } 63 | 64 | /// 65 | /// Invoked when an individual migration is completed successfully 66 | /// 67 | /// Migration which completed 68 | /// Direction of the migration 69 | public void EndMigration(MigrationData migration, MigrationDirection direction) 70 | { 71 | TestContext.WriteLine($"{this.name}:"); 72 | } 73 | 74 | /// 75 | /// Invoked when an individual migration fails with an error 76 | /// 77 | /// Exception which was encountered 78 | /// Migration which failed 79 | /// Direction of the migration 80 | public void EndMigrationWithError(Exception exception, MigrationData migration, MigrationDirection direction) 81 | { 82 | this.WriteError($"{this.name}: {migration.Version}: {migration.FullName} ERROR {exception.Message}"); 83 | } 84 | 85 | /// 86 | /// Invoked when another informative message should be logged 87 | /// 88 | /// Message to be logged 89 | public void Info(string message) 90 | { 91 | TestContext.WriteLine($"{this.name}: {message}"); 92 | } 93 | 94 | /// 95 | /// Invoked when SQL being executed should be logged 96 | /// 97 | /// SQL to log 98 | public void LogSql(string message) 99 | { 100 | if (this.EnableSqlLogging) 101 | TestContext.WriteLine($"{this.name}: {message}"); 102 | } 103 | 104 | private void WriteHeader(string message) 105 | { 106 | TestContext.WriteLine($"{this.name}: {new String('-', 20)}"); 107 | TestContext.WriteLine($"{this.name}: {message}"); 108 | TestContext.WriteLine($"{this.name}: {new String('-', 20)}"); 109 | TestContext.WriteLine($"{this.name}:"); 110 | } 111 | 112 | private void WriteError(string message) 113 | { 114 | TestContext.WriteLine($"{this.name}:"); 115 | TestContext.WriteLine($"{this.name}: {new String('-', 20)}"); 116 | TestContext.WriteLine($"{this.name}: {message}"); 117 | TestContext.WriteLine($"{this.name}: {new String('-', 20)}"); 118 | } 119 | 120 | private void WriteWarning(string message) 121 | { 122 | Console.WriteLine($"{this.name}: {new String('-', Console.WindowWidth - 1)}"); 123 | Console.WriteLine($"{this.name}: {message}"); 124 | Console.WriteLine($"{this.name}: {new String('-', Console.WindowWidth - 1)}"); 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/Simple.Migrations.IntegrationTests/Postgresql/PostgresqlStringsProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Simple.Migrations.IntegrationTests.Postgresql 4 | { 5 | public class PostgresqlStringsProvider : IMigrationStringsProvider 6 | { 7 | public string CreateUsersTableUp => @"CREATE TABLE Users ( 8 | Id SERIAL NOT NULL PRIMARY KEY, 9 | Name TEXT NOT NULL 10 | )"; 11 | public string CreateUsersTableDown => "DROP TABLE Users"; 12 | } 13 | } -------------------------------------------------------------------------------- /src/Simple.Migrations.IntegrationTests/Postgresql/PostgresqlTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using SimpleMigrations; 7 | using NUnit.Framework; 8 | using SimpleMigrations.DatabaseProvider; 9 | using Npgsql; 10 | using System.Data.Common; 11 | 12 | namespace Simple.Migrations.IntegrationTests.Postgresql 13 | { 14 | [TestFixture] 15 | public class PostgresqlTests : TestsBase 16 | { 17 | protected override IDatabaseProvider CreateDatabaseProvider() => new PostgresqlDatabaseProvider(this.CreateConnection()); 18 | 19 | protected override IMigrationStringsProvider MigrationStringsProvider { get; } = new PostgresqlStringsProvider(); 20 | 21 | protected override bool SupportConcurrentMigrators => true; 22 | 23 | protected override void Clean() 24 | { 25 | var connection = this.CreatePgConnection(); 26 | connection.Open(); 27 | 28 | using (var cmd = new NpgsqlCommand(@"DROP SCHEMA IF EXISTS public CASCADE", connection)) 29 | { 30 | cmd.ExecuteNonQuery(); 31 | } 32 | } 33 | 34 | private NpgsqlConnection CreatePgConnection() => new NpgsqlConnection(ConnectionStrings.PostgreSQL); 35 | 36 | protected override DbConnection CreateConnection() => this.CreatePgConnection(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Simple.Migrations.IntegrationTests/Simple.Migrations.IntegrationTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net451 5 | Simple.Migrations.IntegrationTests 6 | Simple.Migrations.IntegrationTests 7 | true 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/Simple.Migrations.IntegrationTests/Sqlite/SqliteStringsProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace Simple.Migrations.IntegrationTests.Sqlite 7 | { 8 | public class SqliteStringsProvider : IMigrationStringsProvider 9 | { 10 | public string CreateUsersTableUp => @"CREATE TABLE Users ( 11 | Id SERIAL NOT NULL PRIMARY KEY, 12 | Name TEXT NOT NULL 13 | )"; 14 | public string CreateUsersTableDown => "DROP TABLE Users"; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Simple.Migrations.IntegrationTests/Sqlite/SqliteTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using SimpleMigrations; 7 | using System.Data; 8 | using SimpleMigrations.DatabaseProvider; 9 | using System.IO; 10 | using System.Reflection; 11 | using Microsoft.Data.Sqlite; 12 | using System.Data.Common; 13 | 14 | namespace Simple.Migrations.IntegrationTests.Sqlite 15 | { 16 | [TestFixture] 17 | public class SqliteTests : TestsBase 18 | { 19 | protected override IDatabaseProvider CreateDatabaseProvider() => new SqliteDatabaseProvider(this.CreateConnection()); 20 | 21 | protected override IMigrationStringsProvider MigrationStringsProvider { get; } = new SqliteStringsProvider(); 22 | 23 | protected override bool SupportConcurrentMigrators => false; 24 | 25 | protected override void Clean() 26 | { 27 | var db = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), ConnectionStrings.SQLiteDatabase); 28 | if (File.Exists(db)) 29 | File.Delete(db); 30 | } 31 | 32 | protected override DbConnection CreateConnection() => new SqliteConnection(ConnectionStrings.SQLite); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Simple.Migrations.IntegrationTests/TestsBase.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using Simple.Migrations.IntegrationTests.Migrations; 3 | using SimpleMigrations; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Data; 7 | using System.Linq; 8 | using System.Reflection; 9 | using System.Threading.Tasks; 10 | using System.Data.Common; 11 | 12 | namespace Simple.Migrations.IntegrationTests 13 | { 14 | public abstract class TestsBase 15 | { 16 | protected abstract DbConnection CreateConnection(); 17 | protected abstract IDatabaseProvider CreateDatabaseProvider(); 18 | protected abstract IMigrationStringsProvider MigrationStringsProvider { get; } 19 | protected abstract void Clean(); 20 | 21 | protected abstract bool SupportConcurrentMigrators { get; } 22 | 23 | [SetUp] 24 | public void SetUp() 25 | { 26 | this.Clean(); 27 | } 28 | 29 | [Test] 30 | public void RunMigration() 31 | { 32 | var migrator = CreateMigrator(typeof(CreateUsersTable)); 33 | migrator.MigrateToLatest(); 34 | } 35 | 36 | [Test] 37 | public void RunsConcurrentMigrationsUp() 38 | { 39 | if (!this.SupportConcurrentMigrators) 40 | Assert.Ignore("Concurrent migrations not supported"); 41 | 42 | Action action = name => 43 | { 44 | var migrator = CreateMigrator(name, typeof(CreateUsersTable)); 45 | migrator.MigrateToLatest(); 46 | }; 47 | 48 | Task.WaitAll( 49 | Task.Run(() => action("migrator1")), 50 | Task.Run(() => action("migrator2")) 51 | ); 52 | } 53 | 54 | [Test] 55 | public void RunsConcurrentMigrationsDown() 56 | { 57 | if (!this.SupportConcurrentMigrators) 58 | Assert.Ignore("Concurrent migrations not supported"); 59 | 60 | var migrator = CreateMigrator("setup", typeof(CreateUsersTable)); 61 | migrator.MigrateToLatest(); 62 | 63 | Action action = name => 64 | { 65 | var thisMigrator = CreateMigrator(name, typeof(CreateUsersTable)); 66 | thisMigrator.MigrateTo(0); 67 | }; 68 | 69 | Task.WaitAll( 70 | Task.Run(() => action("migrator1")), 71 | Task.Run(() => action("migrator2")) 72 | ); 73 | } 74 | 75 | protected SimpleMigrator CreateMigrator(params Type[] migrationTypes) 76 | { 77 | return this.CreateMigrator("migrator", migrationTypes); 78 | } 79 | 80 | protected SimpleMigrator CreateMigrator(string name, params Type[] migrationTypes) 81 | { 82 | foreach (var migrationType in migrationTypes) 83 | { 84 | var setupMethod = migrationType.GetMethod("Setup", BindingFlags.Static | BindingFlags.Public); 85 | setupMethod.Invoke(null, new object[] { this.MigrationStringsProvider }); 86 | } 87 | 88 | var migrationProvider = new CustomMigrationProvider(migrationTypes); 89 | var migrator = new SimpleMigrator(migrationProvider, this.CreateDatabaseProvider(), new NUnitLogger(name)); 90 | migrator.Load(); 91 | return migrator; 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/Simple.Migrations.UnitTests/DatabaseProviderBaseTests.cs: -------------------------------------------------------------------------------- 1 | //using Moq; 2 | //using NUnit.Framework; 3 | //using SimpleMigrations; 4 | //using SimpleMigrations.DatabaseProvider; 5 | //using System; 6 | //using System.Collections.Generic; 7 | //using System.Data; 8 | //using System.Data.Common; 9 | //using System.Linq; 10 | //using System.Threading.Tasks; 11 | 12 | //namespace Simple.Migrations.UnitTests 13 | //{ 14 | // [TestFixture] 15 | // public class DatabaseProviderBaseTests : AssertionHelper 16 | // { 17 | // private class DatabaseProvider : DatabaseProviderBase 18 | // { 19 | // public new int MaxDescriptionLength 20 | // { 21 | // get { return base.MaxDescriptionLength; } 22 | // set { base.MaxDescriptionLength = value; } 23 | // } 24 | 25 | // public string CreateTableSql = "Create Table SQL"; 26 | // public override string GetCreateVersionTableSql() => this.CreateTableSql; 27 | 28 | // public string CurrentVersionSql = "Get Current Version SQL"; 29 | // public override string GetCurrentVersionSql() => this.CurrentVersionSql; 30 | 31 | // public string SetVersionSql = "Set Version SQL"; 32 | // public override string GetSetVersionSql() => this.SetVersionSql; 33 | 34 | // public override DbConnection BeginOperation() 35 | // { 36 | // throw new NotImplementedException(); 37 | // } 38 | 39 | // public override void EndOperation() 40 | // { 41 | // throw new NotImplementedException(); 42 | // } 43 | 44 | // public override long EnsureCreatedAndGetCurrentVersion() 45 | // { 46 | // throw new NotImplementedException(); 47 | // } 48 | 49 | // public override long GetCurrentVersion() 50 | // { 51 | // throw new NotImplementedException(); 52 | // } 53 | 54 | // public override void UpdateVersion(long oldVersion, long newVersion, string newDescription) 55 | // { 56 | // throw new NotImplementedException(); 57 | // } 58 | // } 59 | 60 | // private DatabaseProvider databaseProvider; 61 | // private Mock connection; 62 | 63 | // [SetUp] 64 | // public void SetUp() 65 | // { 66 | // this.databaseProvider = new DatabaseProvider(); 67 | 68 | // this.connection = new Mock(); 69 | // } 70 | 71 | // [Test] 72 | // public void GetCurrentVersionReturnsTheCurrentVersion() 73 | // { 74 | // var command = new Mock(); 75 | // this.connection.Setup(x => x.CreateCommand()).Returns(command.Object); 76 | 77 | // command.Setup(x => x.ExecuteScalar()).Returns((object)4); 78 | 79 | // long version = this.databaseProvider.GetCurrentVersion(); 80 | 81 | // Expect(version, EqualTo(4)); 82 | // command.VerifySet(x => x.CommandText = "Get Current Version SQL"); 83 | // } 84 | 85 | // //[Test] 86 | // //public void GetCurrentVersionReturnsZeroIfCommandReturnsNull() 87 | // //{ 88 | // // this.databaseProvider.SetConnection(this.connection.Object); 89 | 90 | // // var command = new Mock(); 91 | // // this.connection.Setup(x => x.CreateCommand()).Returns(command.Object); 92 | 93 | // // command.Setup(x => x.ExecuteScalar()).Returns(null).Verifiable(); 94 | 95 | // // this.databaseProvider.UseTransaction = false; 96 | // // long version = this.databaseProvider.GetCurrentVersion(); 97 | 98 | // // Expect(version, EqualTo(0)); 99 | // // command.Verify(); 100 | // //} 101 | 102 | // //[Test] 103 | // //public void GetCurrentVersionThrowsMigrationExceptionIfResultCannotBeConvertedToLong() 104 | // //{ 105 | // // this.databaseProvider.SetConnection(this.connection.Object); 106 | 107 | // // var command = new Mock(); 108 | // // this.connection.Setup(x => x.CreateCommand()).Returns(command.Object); 109 | 110 | // // command.Setup(x => x.ExecuteScalar()).Returns("not a number"); 111 | 112 | // // this.databaseProvider.UseTransaction = false; 113 | // // Expect(() => this.databaseProvider.GetCurrentVersion(), Throws.InstanceOf()); 114 | // //} 115 | 116 | // //[Test] 117 | // //public void UpdateVersionThrowsIfConnectionNotSet() 118 | // //{ 119 | // // Expect(() => this.databaseProvider.UpdateVersion(1, 2, "Test"), Throws.InvalidOperationException); 120 | // //} 121 | 122 | // //[Test] 123 | // //public void UpdateVersionUpdatesVersion() 124 | // //{ 125 | // // this.databaseProvider.SetConnection(this.connection.Object); 126 | 127 | // // var command = new Mock(); 128 | // // this.connection.Setup(x => x.CreateCommand()).Returns(command.Object); 129 | 130 | // // var sequence = new MockSequence(); 131 | // // var param1 = new Mock(); 132 | // // var param2 = new Mock(); 133 | // // command.InSequence(sequence).Setup(x => x.CreateParameter()).Returns(param1.Object); 134 | // // command.InSequence(sequence).Setup(x => x.CreateParameter()).Returns(param2.Object); 135 | 136 | // // var commandParameters = new Mock(); 137 | // // command.Setup(x => x.Parameters).Returns(commandParameters.Object); 138 | 139 | // // this.databaseProvider.UseTransaction = true; 140 | // // this.databaseProvider.UpdateVersion(2, 3, "Test Description"); 141 | 142 | // // command.VerifySet(x => x.CommandText = "Set Version SQL"); 143 | // // command.Verify(x => x.ExecuteNonQuery()); 144 | 145 | // // commandParameters.Verify(x => x.Add(param1.Object)); 146 | // // commandParameters.Verify(x => x.Add(param2.Object)); 147 | 148 | // // param1.VerifySet(x => x.ParameterName = "Version"); 149 | // // // Since it's boxed, == doesn't work 150 | // // param1.VerifySet(x => x.Value = It.Is(p => (p is long) && (long)p == 3)); 151 | 152 | // // param2.VerifySet(x => x.ParameterName = "Description"); 153 | // // param2.VerifySet(x => x.Value = "Test Description"); 154 | // //} 155 | 156 | // //[Test] 157 | // //public void UpdateVersionTruncatesDescriptionIfNecessary() 158 | // //{ 159 | // // this.databaseProvider.SetConnection(this.connection.Object); 160 | 161 | // // this.databaseProvider.MaxDescriptionLength = 10; 162 | 163 | // // var command = new Mock(); 164 | // // this.connection.Setup(x => x.CreateCommand()).Returns(command.Object); 165 | 166 | // // var sequence = new MockSequence(); 167 | // // var param1 = new Mock(); 168 | // // var param2 = new Mock(); 169 | // // command.InSequence(sequence).Setup(x => x.CreateParameter()).Returns(param1.Object); 170 | // // command.InSequence(sequence).Setup(x => x.CreateParameter()).Returns(param2.Object); 171 | 172 | // // var commandParameters = new Mock(); 173 | // // command.Setup(x => x.Parameters).Returns(commandParameters.Object); 174 | 175 | // // this.databaseProvider.UpdateVersion(2, 3, "Test Description"); 176 | 177 | // // param2.VerifySet(x => x.Value = "Test De..."); 178 | // //} 179 | // } 180 | //} 181 | -------------------------------------------------------------------------------- /src/Simple.Migrations.UnitTests/MigrationTests.cs: -------------------------------------------------------------------------------- 1 | using Moq; 2 | using NUnit.Framework; 3 | using SimpleMigrations; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Data; 7 | using System.Data.Common; 8 | using System.Linq; 9 | using System.Threading.Tasks; 10 | 11 | namespace Simple.Migrations.UnitTests 12 | { 13 | [TestFixture] 14 | public class MigrationTests : AssertionHelper 15 | { 16 | private Mock migration; 17 | private Mock logger; 18 | private Mock connection; 19 | 20 | [SetUp] 21 | public void SetUp() 22 | { 23 | this.logger = new Mock(); 24 | this.connection = new Mock(); 25 | 26 | this.migration = new Mock() 27 | { 28 | CallBase = true, 29 | }; 30 | //this.migration.Object.Logger = this.logger.Object; 31 | //this.migration.Object.DB = this.connection.Object; 32 | } 33 | 34 | //[Test] 35 | //public void ExecuteThrowsIfDbIsNull() 36 | //{ 37 | // this.migration.Object.DB = null; 38 | // Expect(() => this.migration.Object.Execute("foo"), Throws.InvalidOperationException); 39 | //} 40 | 41 | //[Test] 42 | //public void ThrowsIfLoggerIsNull() 43 | //{ 44 | // this.migration.Object.Logger = null; 45 | // Expect(() => this.migration.Object.Execute("foo"), Throws.InvalidOperationException); 46 | //} 47 | 48 | //[Test] 49 | //public void ExecuteLogsSql() 50 | //{ 51 | // this.connection.Setup(x => x.CreateCommand()).Returns(new Mock().Object); 52 | 53 | // this.migration.Object.Execute("some sql"); 54 | 55 | // this.logger.Verify(x => x.LogSql("some sql")); 56 | //} 57 | 58 | //[Test] 59 | //public void ExecuteExecutesCommand() 60 | //{ 61 | // var command = new Mock(); 62 | // this.connection.Setup(x => x.CreateCommand()).Returns(command.Object); 63 | 64 | // this.migration.Object.Execute("some sql"); 65 | 66 | // command.VerifySet(x => x.CommandText = "some sql"); 67 | // command.Verify(x => x.ExecuteNonQuery()); 68 | //} 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/Simple.Migrations.UnitTests/MockDatabaseProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using SimpleMigrations; 6 | using System.Data.Common; 7 | 8 | namespace Simple.Migrations.UnitTests 9 | { 10 | public abstract class MockDatabaseProvider : IDatabaseProvider 11 | { 12 | public long CurrentVersion; 13 | public DbConnection Connection; 14 | 15 | public DbConnection BeginOperation() 16 | { 17 | return this.Connection; 18 | } 19 | 20 | public void EndOperation() 21 | { 22 | } 23 | 24 | public abstract void EnsureCreated(); 25 | 26 | public long EnsurePrerequisitesCreatedAndGetCurrentVersion() 27 | { 28 | return this.CurrentVersion; 29 | } 30 | 31 | public virtual long GetCurrentVersion() 32 | { 33 | return this.CurrentVersion; 34 | } 35 | 36 | public virtual void UpdateVersion(long oldVersion, long newVersion, string newDescription) 37 | { 38 | this.CurrentVersion = newVersion; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Simple.Migrations.UnitTests/Simple.Migrations.UnitTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net451 4 | Simple.Migrations.UnitTests 5 | Simple.Migrations.UnitTests 6 | true 7 | false 8 | false 9 | false 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/Simple.Migrations.UnitTests/SimpleMigratorLoadTests.cs: -------------------------------------------------------------------------------- 1 | using Moq; 2 | using NUnit.Framework; 3 | using SimpleMigrations; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Data; 7 | using System.Data.Common; 8 | using System.Linq; 9 | using System.Reflection; 10 | using System.Threading.Tasks; 11 | 12 | namespace Simple.Migrations.UnitTests 13 | { 14 | [TestFixture] 15 | public class SimpleMigratorLoadTests : AssertionHelper 16 | { 17 | private class MigrationNotDerivedFromBase : IMigration 18 | { 19 | public void RunMigration(MigrationRunData data) 20 | { 21 | throw new NotImplementedException(); 22 | } 23 | } 24 | 25 | private class ValidMigration1 : Migration 26 | { 27 | protected override void Down() { } 28 | protected override void Up() { } 29 | } 30 | 31 | private class ValidMigration2 : Migration 32 | { 33 | protected override void Down() { } 34 | protected override void Up() { } 35 | } 36 | 37 | private Mock connection; 38 | private Mock migrationProvider; 39 | private Mock> databaseProvider; 40 | 41 | private SimpleMigrator migrator; 42 | 43 | [SetUp] 44 | public void SetUp() 45 | { 46 | this.connection = new Mock(); 47 | this.migrationProvider = new Mock(); 48 | this.databaseProvider = new Mock>(); 49 | 50 | this.migrator = new SimpleMigrator(this.migrationProvider.Object, this.databaseProvider.Object); 51 | } 52 | 53 | [Test] 54 | public void LoadThrowsIfMigrationProviderDoesNotFindAnyMigrations() 55 | { 56 | this.migrationProvider.Setup(x => x.LoadMigrations()).Returns>(null); 57 | Expect(() => this.migrator.Load(), Throws.InstanceOf()); 58 | 59 | this.migrationProvider.Setup(x => x.LoadMigrations()).Returns(new List()); 60 | Expect(() => this.migrator.Load(), Throws.InstanceOf()); 61 | } 62 | 63 | [Test] 64 | public void LoadThrowsIfMigrationProviderProvidesATypeNotDerivedFromTMigration() 65 | { 66 | var migrations = new List() 67 | { 68 | new MigrationData(1, "Migration", typeof(MigrationNotDerivedFromBase).GetTypeInfo()), 69 | }; 70 | this.migrationProvider.Setup(x => x.LoadMigrations()).Returns(migrations); 71 | 72 | Expect(() => this.migrator.Load(), Throws.InstanceOf()); 73 | } 74 | 75 | [Test] 76 | public void LoadThrowsIfAnyMigrationHasAnInvalidVersion() 77 | { 78 | var migrations = new List() 79 | { 80 | new MigrationData(0, "Migration", typeof(ValidMigration1).GetTypeInfo()), 81 | }; 82 | this.migrationProvider.Setup(x => x.LoadMigrations()).Returns(migrations); 83 | 84 | Expect(() => this.migrator.Load(), Throws.InstanceOf()); 85 | } 86 | 87 | [Test] 88 | public void LoadThrowsIfTwoMigrationsHaveTheSameVersion() 89 | { 90 | var migrations = new List() 91 | { 92 | new MigrationData(2, "Migration", typeof(ValidMigration1).GetTypeInfo()), 93 | new MigrationData(2, "Migration", typeof(ValidMigration2).GetTypeInfo()), 94 | }; 95 | this.migrationProvider.Setup(x => x.LoadMigrations()).Returns(migrations); 96 | 97 | Expect(() => this.migrator.Load(), Throws.InstanceOf()); 98 | } 99 | 100 | [Test] 101 | public void LoadCorrectlyLoadsAndSortsMigrations() 102 | { 103 | var migrations = new List() 104 | { 105 | new MigrationData(2, "Migration 2", typeof(ValidMigration2).GetTypeInfo()), 106 | new MigrationData(1, "Migration 1", typeof(ValidMigration1).GetTypeInfo()), 107 | }; 108 | this.migrationProvider.Setup(x => x.LoadMigrations()).Returns(migrations); 109 | 110 | this.migrator.Load(); 111 | 112 | Expect(this.migrator.Migrations.Count, EqualTo(3)); 113 | Expect(this.migrator.Migrations[0].Version, EqualTo(0)); 114 | Expect(this.migrator.Migrations[1], EqualTo(migrations[1])); 115 | Expect(this.migrator.Migrations[2], EqualTo(migrations[0])); 116 | 117 | Expect(this.migrator.LatestMigration, EqualTo(migrations[0])); 118 | } 119 | 120 | [Test] 121 | public void LoadThrowsIfCurrentMigrationVersionNotFound() 122 | { 123 | var migrations = new List() 124 | { 125 | new MigrationData(2, "Migration 1", typeof(ValidMigration1).GetTypeInfo()), 126 | }; 127 | this.migrationProvider.Setup(x => x.LoadMigrations()).Returns(migrations); 128 | 129 | this.databaseProvider.Setup(x => x.EnsurePrerequisitesCreatedAndGetCurrentVersion()).Returns(1); 130 | 131 | Assert.That(() => this.migrator.Load(), Throws.InstanceOf()); 132 | } 133 | 134 | [Test] 135 | public void LoadCorrectlyIdentifiesCurrentMigration() 136 | { 137 | var migrations = new List() 138 | { 139 | new MigrationData(1, "Migration 1", typeof(ValidMigration1).GetTypeInfo()), 140 | new MigrationData(2, "Migration 2", typeof(ValidMigration2).GetTypeInfo()), 141 | }; 142 | this.migrationProvider.Setup(x => x.LoadMigrations()).Returns(migrations); 143 | this.databaseProvider.Setup(x => x.EnsurePrerequisitesCreatedAndGetCurrentVersion()).Returns(2); 144 | 145 | this.migrator.Load(); 146 | 147 | Expect(this.migrator.CurrentMigration, EqualTo(migrations[1])); 148 | } 149 | 150 | [Test] 151 | public void CallingLoadTwiceDoesNothing() 152 | { 153 | var migrations = new List() 154 | { 155 | new MigrationData(1, "Migration 1", typeof(ValidMigration1).GetTypeInfo()), 156 | }; 157 | this.migrationProvider.Setup(x => x.LoadMigrations()).Returns(migrations); 158 | 159 | this.migrator.Load(); 160 | this.migrator.Load(); 161 | 162 | this.migrationProvider.Verify(x => x.LoadMigrations(), Times.Exactly(1)); 163 | } 164 | 165 | [Test] 166 | public void LoadCreatesVersionTable() 167 | { 168 | var migrations = new List() 169 | { 170 | new MigrationData(1, "Migration 1", typeof(ValidMigration1).GetTypeInfo()), 171 | }; 172 | this.migrationProvider.Setup(x => x.LoadMigrations()).Returns(migrations); 173 | 174 | this.migrator.Load(); 175 | 176 | this.databaseProvider.Verify(x => x.EnsurePrerequisitesCreatedAndGetCurrentVersion()); 177 | } 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /src/Simple.Migrations.UnitTests/SimpleMigratorMigrateTests.cs: -------------------------------------------------------------------------------- 1 | using Moq; 2 | using NUnit.Framework; 3 | using SimpleMigrations; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Data.Common; 7 | using System.Linq; 8 | using System.Reflection; 9 | using System.Threading.Tasks; 10 | 11 | namespace Simple.Migrations.UnitTests 12 | { 13 | [TestFixture] 14 | public class SimpleMigratorMigrateTests : AssertionHelper 15 | { 16 | private class Migration1 : Migration 17 | { 18 | public static bool UpCalled; 19 | public static bool DownCalled; 20 | 21 | public static Exception Exception; 22 | public static Action Callback; 23 | 24 | public new DbConnection Connection => base.Connection; 25 | public new IMigrationLogger Logger => base.Logger; 26 | 27 | public static void Reset() 28 | { 29 | UpCalled = false; 30 | DownCalled = false; 31 | Exception = null; 32 | Callback = null; 33 | } 34 | 35 | protected override void Up() 36 | { 37 | UpCalled = true; 38 | Callback?.Invoke(this); 39 | if (Exception != null) 40 | { 41 | throw Exception; 42 | } 43 | } 44 | 45 | protected override void Down() 46 | { 47 | DownCalled = true; 48 | Callback?.Invoke(this); 49 | if (Exception != null) 50 | { 51 | throw Exception; 52 | } 53 | } 54 | } 55 | 56 | private class Migration2 : Migration 57 | { 58 | public static bool UpCalled; 59 | public static bool DownCalled; 60 | 61 | public static void Reset() 62 | { 63 | UpCalled = false; 64 | DownCalled = false; 65 | } 66 | 67 | protected override void Down() { DownCalled = true; } 68 | protected override void Up() { UpCalled = true; } 69 | } 70 | 71 | private Mock connection; 72 | private Mock migrationProvider; 73 | private Mock databaseProvider; 74 | private Mock logger; 75 | 76 | private List migrations; 77 | 78 | private SimpleMigrator migrator; 79 | 80 | [SetUp] 81 | public void SetUp() 82 | { 83 | Migration1.Reset(); 84 | Migration2.Reset(); 85 | 86 | this.connection = new Mock() 87 | { 88 | DefaultValue = DefaultValue.Mock, 89 | }; 90 | this.migrationProvider = new Mock(); 91 | this.databaseProvider = new Mock() 92 | { 93 | CallBase = true, 94 | }; 95 | this.databaseProvider.Object.Connection = this.connection.Object; 96 | 97 | this.logger = new Mock(); 98 | 99 | this.migrator = new SimpleMigrator(this.migrationProvider.Object, this.databaseProvider.Object, this.logger.Object); 100 | 101 | this.migrations = new List() 102 | { 103 | new MigrationData(1, "Migration 1", typeof(Migration1).GetTypeInfo()), 104 | new MigrationData(2, "Migration 2", typeof(Migration2).GetTypeInfo()), 105 | }; 106 | this.migrationProvider.Setup(x => x.LoadMigrations()).Returns(this.migrations); 107 | } 108 | 109 | [Test] 110 | public void BaselineThrowsIfMigrationsHaveAlreadyBeenApplied() 111 | { 112 | this.LoadMigrator(1); 113 | 114 | Expect(() => this.migrator.Baseline(1), Throws.InstanceOf()); 115 | } 116 | 117 | [Test] 118 | public void BaselineThrowsIfRequestedVersionDoesNotExist() 119 | { 120 | this.databaseProvider.Object.CurrentVersion = 0; 121 | this.migrator.Load(); 122 | 123 | Expect(() => this.migrator.Baseline(3), Throws.InstanceOf().With.Property("Version").EqualTo(3)); 124 | } 125 | 126 | [Test] 127 | public void BaselineBaselinesDatabase() 128 | { 129 | this.LoadMigrator(0); 130 | 131 | this.migrator.Baseline(1); 132 | 133 | this.databaseProvider.Verify(x => x.UpdateVersion(0, 1, "Migration1 (Migration 1)")); 134 | Expect(this.migrator.CurrentMigration, EqualTo(this.migrations[0])); 135 | } 136 | 137 | [Test] 138 | public void MigrateToThrowsIfVersionNotFound() 139 | { 140 | this.LoadMigrator(0); 141 | 142 | Expect(() => this.migrator.MigrateTo(3), Throws.InstanceOf().With.Property("Version").EqualTo(3)); 143 | } 144 | 145 | [Test] 146 | public void MigrateToDoesNothingIfThereIsNothingToDo() 147 | { 148 | this.LoadMigrator(1); 149 | 150 | this.migrator.MigrateTo(1); 151 | 152 | Expect(Migration1.UpCalled, False); 153 | Expect(Migration1.DownCalled, False); 154 | Expect(Migration2.UpCalled, False); 155 | Expect(Migration2.DownCalled, False); 156 | this.databaseProvider.Verify(x => x.UpdateVersion(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never()); 157 | } 158 | 159 | [Test] 160 | public void MigrateToMigratesUpCorrectly() 161 | { 162 | this.LoadMigrator(0); 163 | 164 | int sequence = 0; 165 | this.databaseProvider.Setup(x => x.UpdateVersion(0, 1, "Migration1 (Migration 1)")).Callback(() => 166 | { 167 | Expect(sequence++, EqualTo(0)); 168 | Expect(Migration1.UpCalled, True); 169 | Expect(Migration2.UpCalled, False); 170 | }).Verifiable(); 171 | this.databaseProvider.Setup(x => x.UpdateVersion(1, 2, "Migration2 (Migration 2)")).Callback(() => 172 | { 173 | Expect(sequence++, EqualTo(1)); 174 | Expect(Migration2.UpCalled, True); 175 | }).Verifiable(); 176 | 177 | this.migrator.MigrateTo(2); 178 | 179 | this.databaseProvider.VerifyAll(); 180 | 181 | Expect(Migration1.DownCalled, False); 182 | Expect(Migration2.DownCalled, False); 183 | } 184 | 185 | [Test] 186 | public void MigrateToMigratesDownCorrectly() 187 | { 188 | this.LoadMigrator(2); 189 | 190 | int sequence = 0; 191 | this.databaseProvider.Setup(x => x.UpdateVersion(2, 1, "Migration1 (Migration 1)")).Callback(() => 192 | { 193 | Expect(sequence++, EqualTo(0)); 194 | Expect(Migration2.DownCalled, True); 195 | Expect(Migration1.DownCalled, False); 196 | }).Verifiable(); 197 | this.databaseProvider.Setup(x => x.UpdateVersion(1, 0, "Empty Schema")).Callback(() => 198 | { 199 | Expect(sequence++, EqualTo(1)); 200 | Expect(Migration1.DownCalled, True); 201 | }).Verifiable(); 202 | 203 | this.migrator.MigrateTo(0); 204 | 205 | this.databaseProvider.VerifyAll(); 206 | 207 | Expect(Migration1.UpCalled, False); 208 | Expect(Migration2.UpCalled, False); 209 | } 210 | 211 | [Test] 212 | public void MigrateToPropagatesExceptionFromMigration() 213 | { 214 | this.LoadMigrator(0); 215 | 216 | var e = new Exception("BOOM"); 217 | Migration1.Exception = e; 218 | 219 | Expect(() => this.migrator.MigrateTo(2), Throws.Exception.EqualTo(e)); 220 | 221 | this.databaseProvider.Verify(x => x.UpdateVersion(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never()); 222 | Expect(this.migrator.CurrentMigration.Version, EqualTo(0)); 223 | Expect(Migration1.DownCalled, False); 224 | } 225 | 226 | [Test] 227 | public void MigrateToAssignsConnectionAndLogger() 228 | { 229 | this.LoadMigrator(0); 230 | 231 | Migration1.Callback = x => 232 | { 233 | Expect(x.Connection, EqualTo(this.connection.Object)); 234 | Expect(x.Logger, EqualTo(this.logger.Object)); 235 | }; 236 | 237 | this.migrator.MigrateTo(1); 238 | } 239 | 240 | private void LoadMigrator(long currentVersion) 241 | { 242 | this.databaseProvider.Object.CurrentVersion = currentVersion; 243 | this.migrator.Load(); 244 | } 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /src/Simple.Migrations/AssemblyMigrationProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | 6 | namespace SimpleMigrations 7 | { 8 | /// 9 | /// which finds migrations by scanning an assembly, optionally 10 | /// filtering by namespace. 11 | /// 12 | public class AssemblyMigrationProvider : IMigrationProvider 13 | { 14 | private readonly Assembly migrationAssembly; 15 | private readonly string migrationNamespace; 16 | 17 | /// 18 | /// Instantiates a new instance of the class 19 | /// 20 | /// Assembly to scan for migrations 21 | /// Optional namespace. If specified, only finds migrations in that namespace 22 | public AssemblyMigrationProvider(Assembly migrationAssembly, string migrationNamespace = null) 23 | { 24 | if (migrationAssembly == null) 25 | throw new ArgumentNullException(nameof(migrationAssembly)); 26 | 27 | this.migrationAssembly = migrationAssembly; 28 | this.migrationNamespace = migrationNamespace; 29 | } 30 | 31 | /// 32 | /// Load all migration info. These can be in any order 33 | /// 34 | /// All migration info 35 | public IEnumerable LoadMigrations() 36 | { 37 | IEnumerable definedTypes; 38 | 39 | // See http://haacked.com/archive/2012/07/23/get-all-types-in-an-assembly.aspx/ 40 | try 41 | { 42 | definedTypes = this.migrationAssembly.DefinedTypes; 43 | } 44 | catch (ReflectionTypeLoadException e) 45 | { 46 | definedTypes = e.Types.Where(t => t != null).Select(t => t.GetTypeInfo()); 47 | } 48 | 49 | var migrations = from type in definedTypes 50 | let attribute = type.GetCustomAttribute() 51 | where attribute != null 52 | where this.migrationNamespace == null || type.Namespace == this.migrationNamespace 53 | select new MigrationData(attribute.Version, attribute.Description, type); 54 | 55 | if (!migrations.Any()) 56 | throw new MigrationException($"Could not find any migrations in the assembly you provided ({this.migrationAssembly.GetName().Name}). Migrations must be decorated with [Migration]"); 57 | 58 | return migrations; 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Simple.Migrations/Console/ConsoleLogger.cs: -------------------------------------------------------------------------------- 1 | namespace SimpleMigrations.Console 2 | { 3 | // Avoid pain with Console.XXX being assumed to be in SimpleMigrations.Console 4 | using System; 5 | 6 | /// 7 | /// implementation which logs to the console 8 | /// 9 | public class ConsoleLogger : ILogger 10 | { 11 | private const int defaultWindowWidth = 80; 12 | 13 | private ConsoleColor foregroundColor; 14 | 15 | /// 16 | /// Gets or sets a value indicating whether SQL should be logged 17 | /// 18 | public bool EnableSqlLogging { get; set; } = true; 19 | 20 | private static int GetWindowWidth() 21 | { 22 | // This will fail if there's no window, for example 23 | 24 | int width = defaultWindowWidth; 25 | try 26 | { 27 | width = Console.WindowWidth; 28 | } 29 | catch { } 30 | 31 | // .NET Core apparently returns a width of 0, and when we do 'width - 1' below this crashes 32 | return Math.Max(1, width); 33 | } 34 | 35 | /// 36 | /// Invoked when a sequence of migrations is started 37 | /// 38 | /// Migration being migrated from 39 | /// Migration being migrated to 40 | public void BeginSequence(MigrationData from, MigrationData to) 41 | { 42 | this.foregroundColor = Console.ForegroundColor; 43 | 44 | this.WriteHeader($"Migrating from {from.Version}: {from.FullName} to {to.Version}: {to.FullName}"); 45 | } 46 | 47 | /// 48 | /// Invoked when a sequence of migrations is completed successfully 49 | /// 50 | /// Migration which was migrated from 51 | /// Migration which was migrated to 52 | public void EndSequence(MigrationData from, MigrationData to) 53 | { 54 | this.WriteHeader("Done"); 55 | } 56 | 57 | /// 58 | /// Invoked when a sequence of migrations fails with an error 59 | /// 60 | /// Exception which was encountered 61 | /// Migration which was migrated from 62 | /// Last successful migration which was applied 63 | public void EndSequenceWithError(Exception exception, MigrationData from, MigrationData currentVersion) 64 | { 65 | Console.WriteLine(); 66 | Console.ForegroundColor = ConsoleColor.Red; 67 | Console.WriteLine("Database is currently on version {0}: {1}", currentVersion.Version, currentVersion.FullName); 68 | Console.ForegroundColor = this.foregroundColor; 69 | } 70 | 71 | /// 72 | /// Invoked when an individual migration is started 73 | /// 74 | /// Migration being started 75 | /// Direction of the migration 76 | public void BeginMigration(MigrationData migration, MigrationDirection direction) 77 | { 78 | var term = direction == MigrationDirection.Up ? "migrating" : "reverting"; 79 | this.WriteHeader($"{migration.Version}: {migration.FullName} {term}"); 80 | } 81 | 82 | /// 83 | /// Invoked when an individual migration is completed successfully 84 | /// 85 | /// Migration which completed 86 | /// Direction of the migration 87 | public void EndMigration(MigrationData migration, MigrationDirection direction) 88 | { 89 | Console.WriteLine(); 90 | } 91 | 92 | /// 93 | /// Invoked when an individual migration fails with an error 94 | /// 95 | /// Exception which was encountered 96 | /// Migration which failed 97 | /// Direction of the migration 98 | public void EndMigrationWithError(Exception exception, MigrationData migration, MigrationDirection direction) 99 | { 100 | this.WriteError($"{migration.Version}: {migration.FullName} ERROR {exception.Message}"); 101 | } 102 | 103 | /// 104 | /// Invoked when another informative message should be logged 105 | /// 106 | /// Message to be logged 107 | public void Info(string message) 108 | { 109 | Console.WriteLine(message); 110 | } 111 | 112 | /// 113 | /// Invoked when SQL being executed should be logged 114 | /// 115 | /// SQL to log 116 | public void LogSql(string message) 117 | { 118 | if (this.EnableSqlLogging) 119 | Console.WriteLine(message); 120 | } 121 | 122 | private void WriteHeader(string message) 123 | { 124 | var width = GetWindowWidth(); 125 | 126 | Console.ForegroundColor = ConsoleColor.Green; 127 | Console.WriteLine(new String('-', width - 1)); 128 | Console.WriteLine(message); 129 | Console.WriteLine(new String('-', width - 1)); 130 | Console.WriteLine(); 131 | Console.ForegroundColor = this.foregroundColor; 132 | } 133 | 134 | private void WriteError(string message) 135 | { 136 | var width = GetWindowWidth(); 137 | 138 | Console.WriteLine(); 139 | Console.ForegroundColor = ConsoleColor.Red; 140 | Console.WriteLine(new String('-', width - 1)); 141 | Console.WriteLine(message); 142 | Console.WriteLine(new String('-', width - 1)); 143 | Console.ForegroundColor = this.foregroundColor; 144 | } 145 | 146 | private void WriteWarning(string message) 147 | { 148 | var width = GetWindowWidth(); 149 | 150 | Console.ForegroundColor = ConsoleColor.Yellow; 151 | Console.WriteLine(new String('-', width - 1)); 152 | Console.WriteLine(message); 153 | Console.WriteLine(new String('-', width - 1)); 154 | Console.ForegroundColor = this.foregroundColor; 155 | } 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/Simple.Migrations/Console/ConsoleRunner.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Collections.Generic; 3 | 4 | namespace SimpleMigrations.Console 5 | { 6 | // Avoid pain with Console.XXX being assumed to be in SimpleMigrations.Console 7 | using System; 8 | 9 | /// 10 | /// Class which provides an easy way to run migrations from a console application 11 | /// 12 | public class ConsoleRunner 13 | { 14 | private readonly ISimpleMigrator migrator; 15 | 16 | /// 17 | /// Gets or sets the subcommand to run if no subcommand is given. If null, help will be printed 18 | /// 19 | public SubCommand DefaultSubCommand { get; set; } 20 | 21 | /// 22 | /// Gets a list of subcommands which are supported 23 | /// 24 | public List SubCommands { get; private set; } 25 | 26 | /// 27 | /// Instantiates a new instance of the class 28 | /// 29 | /// Migrator to use 30 | public ConsoleRunner(ISimpleMigrator migrator) 31 | { 32 | this.migrator = migrator; 33 | 34 | // If they haven't assigned a logger, do so now 35 | if (this.migrator.Logger == null) 36 | this.migrator.Logger = new ConsoleLogger(); 37 | 38 | this.CreateSubCommands(); 39 | } 40 | 41 | /// 42 | /// Assign (and optionally ) 43 | /// 44 | protected virtual void CreateSubCommands() 45 | { 46 | this.SubCommands = new List() 47 | { 48 | new SubCommand() 49 | { 50 | Command = "up", 51 | Description = "up: Migrate to the latest version", 52 | Action = this.MigrateUp, 53 | }, 54 | new SubCommand() 55 | { 56 | Command = "to", 57 | Description = "to : Migrate up/down to the version n", 58 | Action = this.MigrateTo 59 | }, 60 | new SubCommand() 61 | { 62 | Command = "reapply", 63 | Description = "reapply: Re-apply the current migration", 64 | Action = this.ReApply 65 | }, 66 | new SubCommand() 67 | { 68 | Command = "list", 69 | Description = "list: List all available migrations", 70 | Action = this.ListMigrations 71 | }, 72 | new SubCommand() 73 | { 74 | Command = "baseline", 75 | Description = "baseline : Move the database to version n, without apply migrations", 76 | Action = this.Baseline 77 | }, 78 | }; 79 | 80 | this.DefaultSubCommand = this.SubCommands[0]; 81 | } 82 | 83 | /// 84 | /// Show help 85 | /// 86 | public virtual void ShowHelp() 87 | { 88 | Console.WriteLine("Usage: Subcommand [-q]"); 89 | Console.WriteLine(); 90 | Console.WriteLine("Subcommand can be one of:"); 91 | foreach (var subCommand in this.SubCommands) 92 | { 93 | Console.WriteLine($" {subCommand.Description}"); 94 | } 95 | } 96 | 97 | /// 98 | /// Parse a migration version from a string 99 | /// 100 | /// String to parse 101 | /// Parsed migration version 102 | public static long ParseVersion(string input) 103 | { 104 | long migration; 105 | if (!Int64.TryParse(input, out migration) || migration < 0) 106 | { 107 | throw new Exception("Migrations must be positive numbers"); 108 | } 109 | 110 | return migration; 111 | } 112 | 113 | /// 114 | /// Run the with the given command-line arguments 115 | /// 116 | /// Command-line arguments to use 117 | /// An exit status: 0 on success, another number on failure 118 | public virtual int Run(string[] args) 119 | { 120 | var argsList = args.ToList(); 121 | 122 | void WriteError(string message) 123 | { 124 | var foregroundColor = Console.ForegroundColor; 125 | Console.ForegroundColor = ConsoleColor.Red; 126 | Console.Error.WriteLine(message); 127 | Console.ForegroundColor = foregroundColor; 128 | } 129 | 130 | try 131 | { 132 | this.migrator.Load(); 133 | 134 | if (argsList.Count == 0) 135 | { 136 | if (this.DefaultSubCommand == null) 137 | throw new HelpNeededException(); 138 | else 139 | this.DefaultSubCommand.Action(argsList); 140 | } 141 | else 142 | { 143 | var subCommandType = argsList[0]; 144 | argsList.RemoveAt(0); 145 | 146 | var subCommand = this.SubCommands.FirstOrDefault(x => x.Command == subCommandType); 147 | if (subCommand == null) 148 | throw new HelpNeededException(); 149 | 150 | subCommand.Action(argsList); 151 | } 152 | 153 | return 0; 154 | } 155 | catch (HelpNeededException) 156 | { 157 | this.ShowHelp(); 158 | return 0; 159 | } 160 | catch (MigrationNotFoundException e) 161 | { 162 | WriteError($"Error: could not find migration with version {e.Version}"); 163 | } 164 | catch (MigrationException e) 165 | { 166 | WriteError($"Error: {e.Message}"); 167 | } 168 | catch (Exception e) 169 | { 170 | WriteError("An unhandled exception occurred:"); 171 | WriteError(e.ToString()); 172 | } 173 | 174 | return 1; 175 | } 176 | 177 | private void MigrateUp(List args) 178 | { 179 | if (args.Count != 0) 180 | throw new HelpNeededException(); 181 | 182 | if (this.migrator.CurrentMigration.Version == this.migrator.LatestMigration.Version) 183 | Console.WriteLine("Nothing to do"); 184 | else 185 | this.migrator.MigrateToLatest(); 186 | } 187 | 188 | private void MigrateTo(List args) 189 | { 190 | if (args.Count != 1) 191 | throw new HelpNeededException(); 192 | 193 | long version = ParseVersion(args[0]); 194 | if (version == this.migrator.CurrentMigration.Version) 195 | Console.WriteLine("Nothing to do"); 196 | else 197 | this.migrator.MigrateTo(version); 198 | } 199 | 200 | private void ReApply(List args) 201 | { 202 | if (args.Count != 0) 203 | throw new HelpNeededException(); 204 | 205 | var currentVersion = this.migrator.CurrentMigration.Version; 206 | var previousVersion = this.migrator.Migrations.TakeWhile(x => x.Version < currentVersion).LastOrDefault(); 207 | 208 | if (previousVersion == null) 209 | throw new MigrationException($"The current version {currentVersion} is the lowest, and so cannot be reapplied"); 210 | 211 | this.migrator.MigrateTo(previousVersion.Version); 212 | this.migrator.MigrateTo(currentVersion); 213 | } 214 | 215 | private void ListMigrations(List args) 216 | { 217 | if (args.Count != 0) 218 | throw new HelpNeededException(); 219 | 220 | Console.WriteLine("Available migrations:"); 221 | 222 | foreach (var migration in this.migrator.Migrations) 223 | { 224 | var marker = (migration == this.migrator.CurrentMigration) ? "*" : " "; 225 | Console.WriteLine($" {marker} {migration.Version}: {migration.FullName}"); 226 | } 227 | } 228 | 229 | private void Baseline(List args) 230 | { 231 | if (args.Count != 1) 232 | throw new HelpNeededException(); 233 | 234 | long version = ParseVersion(args[0]); 235 | 236 | this.migrator.Baseline(version); 237 | Console.WriteLine($"Database versioned at version {version}"); 238 | } 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /src/Simple.Migrations/Console/HelpNeededException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SimpleMigrations.Console 4 | { 5 | /// 6 | /// Throw this from a to show help to the user 7 | /// 8 | public class HelpNeededException : Exception 9 | { 10 | /// 11 | /// Instantiates a new instance of the class 12 | /// 13 | public HelpNeededException() 14 | { 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Simple.Migrations/Console/SubCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace SimpleMigrations.Console 4 | { 5 | /// 6 | /// Signature for the actino of a 7 | /// 8 | /// Additional command-line arguments 9 | public delegate void SubCommandAction(List args); 10 | 11 | /// 12 | /// A SubCommand, used by the class 13 | /// 14 | public class SubCommand 15 | { 16 | /// 17 | /// Gets and sets the sub-command, which when matched will cause this subcommand to run 18 | /// 19 | public string Command { get; set; } 20 | 21 | /// 22 | /// Gets and sets the description of the sub-command, which is shown to the user 23 | /// 24 | public string Description { get; set; } 25 | 26 | /// 27 | /// Gets and sets the action to invoke when the user executes the sub-command 28 | /// 29 | public SubCommandAction Action { get; set; } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Simple.Migrations/DatabaseProvider/DatabaseProviderBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data.Common; 3 | 4 | namespace SimpleMigrations.DatabaseProvider 5 | { 6 | /// 7 | /// Database provider which acts by maintaining a table of applied versions 8 | /// 9 | /// 10 | /// Methods on this class are called according to a strict sequence. 11 | /// 12 | /// When is called: 13 | /// 1. is invoked 14 | /// 15 | /// 16 | /// When or 17 | /// is called: 18 | /// 1. is called. 19 | /// 2. is called. 20 | /// 3. is called (potentially multiple times) 21 | /// 4. is called. 22 | /// 5. is called. 23 | /// 24 | /// Different databases require different locking strategies to guard against concurrent migrators. 25 | /// 26 | /// Databases which support advisory locks typically have a single connection, and use this for obtaining 27 | /// the advisory lock, running migrations, and updating the VersionInfo table. The advisory lock is obtained 28 | /// when creating the VersionInfo table also. 29 | /// 30 | /// Databases which do not support advisory locks typically have a connection factory. Inside 31 | /// they will create two connections. One creates a transaction and uses it to 32 | /// lock the VersionInfo table, and also to update the VersionInfo table, while the other is used to run migrations. 33 | /// 34 | /// The subclasses and 35 | /// encapsulate these concepts. 36 | /// 37 | public abstract class DatabaseProviderBase : IDatabaseProvider 38 | { 39 | /// 40 | /// Table name used to store version info. Defaults to 'VersionInfo' 41 | /// 42 | public string TableName { get; set; } = "VersionInfo"; 43 | 44 | /// 45 | /// If > 0, specifies the maximum length of the 'Description' field. Descriptions longer will be truncated 46 | /// 47 | /// 48 | /// Database providers which put a maximum length on the Description field should set this to that length 49 | /// 50 | protected int MaxDescriptionLength { get; set; } 51 | 52 | /// 53 | /// Ensures that the schema (if appropriate) and version table are created, and returns the current version. 54 | /// 55 | /// 56 | /// This is not surrounded by calls to or , so 57 | /// it should do whatever locking is appropriate to guard against concurrent migrators. 58 | /// 59 | /// If the version table is empty, this should return 0. 60 | /// 61 | /// The current version, or 0 62 | public abstract long EnsurePrerequisitesCreatedAndGetCurrentVersion(); 63 | 64 | /// 65 | /// Helper method which ensures that the schema and VersionInfo table are created, and fetches the current version from it, 66 | /// using the given connection and transaction. 67 | /// 68 | /// Connection to use 69 | /// Transaction to use 70 | /// The current version, or 0 71 | protected virtual long EnsurePrerequisitesCreatedAndGetCurrentVersion(DbConnection connection, DbTransaction transaction) 72 | { 73 | var createSchemaSql = this.GetCreateSchemaTableSql(); 74 | if (!string.IsNullOrEmpty(createSchemaSql)) 75 | { 76 | using (var command = connection.CreateCommand()) 77 | { 78 | command.CommandText = createSchemaSql; 79 | command.Transaction = transaction; 80 | command.ExecuteNonQuery(); 81 | } 82 | } 83 | 84 | using (var command = connection.CreateCommand()) 85 | { 86 | command.CommandText = this.GetCreateVersionTableSql(); 87 | command.Transaction = transaction; 88 | command.ExecuteNonQuery(); 89 | } 90 | 91 | return this.GetCurrentVersion(connection, transaction); 92 | } 93 | 94 | /// 95 | /// Called when or 96 | /// is invoked, before any migrations are run. This should create connections and/or transactions and/or locks if necessary, 97 | /// and return the connection for the migrations to use. 98 | /// 99 | /// Connection for the migrations to use 100 | public abstract DbConnection BeginOperation(); 101 | 102 | /// 103 | /// Cleans up any connections and/or transactions and/or locks created by 104 | /// 105 | /// 106 | /// This is always paired with a call to : it is called exactly once for every time that 107 | /// is called. 108 | /// 109 | public abstract void EndOperation(); 110 | 111 | /// 112 | /// Fetch the current database schema version, or 0. 113 | /// 114 | /// 115 | /// This method is always invoked after a call to , but before a call to 116 | /// . Therefore it may use a connection created by . 117 | /// 118 | /// If this databases uses locking on the VersionInfo table to guard against concurrent migrators, this 119 | /// method should use the connection that lock was acquired on. 120 | /// 121 | /// The current database schema version, or 0 122 | public abstract long GetCurrentVersion(); 123 | 124 | /// 125 | /// Helper method to fetch the current database version, using the given connection and transaction. 126 | /// 127 | /// 128 | /// The transaction may be null. 129 | /// 130 | /// Connection to use 131 | /// transaction to use, may be null 132 | /// The current database schema version, or 0 133 | protected virtual long GetCurrentVersion(DbConnection connection, DbTransaction transaction) 134 | { 135 | long version = 0; 136 | using (var command = connection.CreateCommand()) 137 | { 138 | command.CommandText = this.GetCurrentVersionSql(); 139 | command.Transaction = transaction; 140 | var result = command.ExecuteScalar(); 141 | 142 | if (result != null) 143 | { 144 | try 145 | { 146 | version = Convert.ToInt64(result); 147 | } 148 | catch 149 | { 150 | throw new MigrationException("Database Provider returns a value for the current version which isn't a long"); 151 | } 152 | } 153 | } 154 | 155 | return version; 156 | } 157 | 158 | /// 159 | /// Update the VersionInfo table to indicate that the given migration was successfully applied. 160 | /// 161 | /// 162 | /// This is always invoked after a call to but before a call to , 163 | /// Therefore it may use a connection created by . 164 | /// 165 | /// The previous version of the database schema 166 | /// The version of the new database schema 167 | /// The description of the migration which was applied 168 | public abstract void UpdateVersion(long oldVersion, long newVersion, string newDescription); 169 | 170 | /// 171 | /// Helper method to update the VersionInfo table to indicate that the given migration was successfully applied, 172 | /// using the given connectoin and transaction. 173 | /// 174 | /// The previous version of the database schema 175 | /// The version of the new database schema 176 | /// The description of the migration which was applied 177 | /// Connection to use 178 | /// Transaction to use, may be null 179 | protected virtual void UpdateVersion(long oldVersion, long newVersion, string newDescription, DbConnection connection, DbTransaction transaction) 180 | { 181 | if (this.MaxDescriptionLength > 0 && newDescription.Length > this.MaxDescriptionLength) 182 | { 183 | newDescription = newDescription.Substring(0, this.MaxDescriptionLength - 3) + "..."; 184 | } 185 | 186 | using (var command = connection.CreateCommand()) 187 | { 188 | command.Transaction = transaction; 189 | command.CommandText = this.GetSetVersionSql(); 190 | 191 | var versionParam = command.CreateParameter(); 192 | versionParam.ParameterName = "Version"; 193 | versionParam.Value = newVersion; 194 | command.Parameters.Add(versionParam); 195 | 196 | var oldVersionParam = command.CreateParameter(); 197 | oldVersionParam.ParameterName = "OldVersion"; 198 | oldVersionParam.Value = oldVersion; 199 | command.Parameters.Add(oldVersionParam); 200 | 201 | var nameParam = command.CreateParameter(); 202 | nameParam.ParameterName = "Description"; 203 | nameParam.Value = newDescription; 204 | command.Parameters.Add(nameParam); 205 | 206 | command.ExecuteNonQuery(); 207 | } 208 | } 209 | 210 | /// 211 | /// Should return 'CREATE SCHEMA IF NOT EXISTS', or similar 212 | /// 213 | /// 214 | /// Don't override if the database has no concept of schemas 215 | /// 216 | protected virtual string GetCreateSchemaTableSql() => null; 217 | 218 | /// 219 | /// Should return 'CREATE TABLE IF NOT EXISTS', or similar 220 | /// 221 | protected abstract string GetCreateVersionTableSql(); 222 | 223 | /// 224 | /// Should return SQL which selects a single long value - the current version - or 0/NULL if there is no current version 225 | /// 226 | protected abstract string GetCurrentVersionSql(); 227 | 228 | /// 229 | /// Returns SQL which upgrades to a particular version. 230 | /// 231 | /// 232 | /// The following parameters may be used: 233 | /// - @Version - the long version to set 234 | /// - @Description - the description of the version 235 | /// - @OldVersion - the long version being migrated from 236 | /// 237 | protected abstract string GetSetVersionSql(); 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /src/Simple.Migrations/DatabaseProvider/DatabaseProviderBaseWithAdvisoryLock.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | using System.Data.Common; 4 | 5 | namespace SimpleMigrations.DatabaseProvider 6 | { 7 | /// 8 | /// subclass for databases which use an advisory lock to guard against concurrent 9 | /// migrators. 10 | /// 11 | /// 12 | /// This uses a single connection, which it does not take ownership of (i.e. it is up to the caller to close it). 13 | /// 14 | public abstract class DatabaseProviderBaseWithAdvisoryLock : DatabaseProviderBase 15 | { 16 | /// 17 | /// Gets the connection used for all database operations 18 | /// 19 | protected DbConnection Connection { get; } 20 | 21 | /// 22 | /// Gets or sets the timeout when acquiring the advisory lock 23 | /// 24 | public TimeSpan LockTimeout { get; set; } = TimeSpan.FromSeconds(600); 25 | 26 | /// 27 | /// Initialises a new instance of the class 28 | /// 29 | /// Database connection to use for all operations 30 | public DatabaseProviderBaseWithAdvisoryLock(DbConnection connection) 31 | { 32 | this.Connection = connection; 33 | if (this.Connection.State != ConnectionState.Open) 34 | this.Connection.Open(); 35 | } 36 | 37 | /// 38 | /// Ensures that the schema (if appropriate) and version table are created, and returns the current version. 39 | /// 40 | /// 41 | /// This is not surrounded by calls to or , so 42 | /// it should do whatever locking is appropriate to guard against concurrent migrators. 43 | /// 44 | /// If the version table is empty, this should return 0. 45 | /// 46 | /// The current version, or 0 47 | public override long EnsurePrerequisitesCreatedAndGetCurrentVersion() 48 | { 49 | try 50 | { 51 | this.AcquireAdvisoryLock(); 52 | 53 | return this.EnsurePrerequisitesCreatedAndGetCurrentVersion(this.Connection, null); 54 | } 55 | finally 56 | { 57 | this.ReleaseAdvisoryLock(); 58 | } 59 | } 60 | 61 | /// 62 | /// Called when or 63 | /// is invoked, before any migrations are run. This invokes to acquire the advisory lock. 64 | /// 65 | /// Connection for the migrations to use 66 | public override DbConnection BeginOperation() 67 | { 68 | this.AcquireAdvisoryLock(); 69 | return this.Connection; 70 | } 71 | 72 | /// 73 | /// Called after migrations are run, this invokes to release the advisory lock. 74 | /// 75 | public override void EndOperation() 76 | { 77 | this.ReleaseAdvisoryLock(); 78 | } 79 | 80 | /// 81 | /// Fetch the current database schema version, or 0. 82 | /// 83 | /// 84 | /// This method is always invoked after a call to , but before a call to 85 | /// . Therefore the advisory lock has already been acquired. 86 | /// 87 | /// The current database schema version, or 0 88 | public override long GetCurrentVersion() 89 | { 90 | return this.GetCurrentVersion(this.Connection, null); 91 | } 92 | 93 | /// 94 | /// Update the VersionInfo table to indicate that the given migration was successfully applied. 95 | /// 96 | /// 97 | /// This is always invoked after a call to but before a call to , 98 | /// Therefore the advisory lock has already been acquired. 99 | /// 100 | /// The previous version of the database schema 101 | /// The version of the new database schema 102 | /// The description of the migration which was applied 103 | public override void UpdateVersion(long oldVersion, long newVersion, string newDescription) 104 | { 105 | this.UpdateVersion(oldVersion, newVersion, newDescription, this.Connection, null); 106 | } 107 | 108 | /// 109 | /// Acquires an advisory lock using 110 | /// 111 | protected abstract void AcquireAdvisoryLock(); 112 | 113 | /// 114 | /// Releases the advisory lock held on 115 | /// 116 | protected abstract void ReleaseAdvisoryLock(); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/Simple.Migrations/DatabaseProvider/DatabaseProviderBaseWithVersionTableLock.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data.Common; 3 | 4 | namespace SimpleMigrations.DatabaseProvider 5 | { 6 | /// 7 | /// subclass for databases which use a transaction on the VersionInfo table to guard 8 | /// against concurrent migrators. 9 | /// 10 | /// 11 | /// This uses two connections for each operation: one which uses a transaction to acquire a lock on the VersionInfo table 12 | /// and to update it, and another to run migrations on. 13 | /// 14 | public abstract class DatabaseProviderBaseWithVersionTableLock : DatabaseProviderBase 15 | { 16 | /// 17 | /// Gets the factory used to create new database connections 18 | /// 19 | protected Func ConnectionFactory { get; } 20 | 21 | /// 22 | /// Gets or sets the connection used to acquire a lock on the VersionInfo table, and to update the VersionInfo 23 | /// table. 24 | /// 25 | /// 26 | /// This is set by , and cleated by . 27 | /// 28 | protected DbConnection VersionTableConnection { get; set; } 29 | 30 | /// 31 | /// Gets or sets the transaction on the used to lock it. 32 | /// 33 | /// 34 | /// This is set by , and cleated by . 35 | /// 36 | protected DbTransaction VersionTableLockTransaction { get; set; } 37 | 38 | /// 39 | /// Gets or sets the connection to be used by migrations. 40 | /// 41 | /// 42 | /// This is set by , and cleated by . 43 | /// 44 | protected DbConnection MigrationsConnection { get; set; } 45 | 46 | /// 47 | /// Initialises a new instance of the class 48 | /// 49 | /// Factory to be used to create new connections 50 | public DatabaseProviderBaseWithVersionTableLock(Func connectionFactory) 51 | { 52 | this.ConnectionFactory = connectionFactory; 53 | } 54 | 55 | /// 56 | /// Ensures that the version table is created, and returns the current version. 57 | /// 58 | /// 59 | /// This is not surrounded by calls to or , so 60 | /// it should create its own connection. 61 | /// 62 | /// If the version table is empty, this should return 0. 63 | /// 64 | /// The current version, or 0 65 | public override long EnsurePrerequisitesCreatedAndGetCurrentVersion() 66 | { 67 | using (var connection = this.ConnectionFactory()) 68 | { 69 | connection.Open(); 70 | 71 | return this.EnsurePrerequisitesCreatedAndGetCurrentVersion(connection, null); 72 | } 73 | } 74 | 75 | /// 76 | /// Called when or 77 | /// is invoked, before any migrations are run. This creates the and 78 | /// , and invokes to acquire the VersionInfo table lock. 79 | /// 80 | /// Connection for the migrations to use 81 | public override DbConnection BeginOperation() 82 | { 83 | this.VersionTableConnection = this.ConnectionFactory(); 84 | this.VersionTableConnection.Open(); 85 | 86 | this.MigrationsConnection = this.ConnectionFactory(); 87 | this.MigrationsConnection.Open(); 88 | 89 | this.AcquireVersionTableLock(); 90 | 91 | return this.MigrationsConnection; 92 | } 93 | 94 | /// 95 | /// Called after migrations are run, this invokes to release the VersionInfo table lock, and 96 | /// then closes and "/> 97 | /// 98 | public override void EndOperation() 99 | { 100 | this.ReleaseVersionTableLock(); 101 | 102 | this.VersionTableConnection.Dispose(); 103 | this.VersionTableConnection = null; 104 | 105 | this.MigrationsConnection.Dispose(); 106 | this.MigrationsConnection = null; 107 | } 108 | 109 | /// 110 | /// Fetch the current database schema version, or 0. 111 | /// 112 | /// 113 | /// This method is always invoked after a call to , but before a call to 114 | /// . It should use and 115 | /// 116 | /// The current database schema version, or 0 117 | public override long GetCurrentVersion() 118 | { 119 | return this.GetCurrentVersion(this.VersionTableConnection, this.VersionTableLockTransaction); 120 | } 121 | 122 | /// 123 | /// Update the VersionInfo table to indicate that the given migration was successfully applied. 124 | /// 125 | /// 126 | /// This is always invoked after a call to but before a call to , 127 | /// It should use and 128 | /// 129 | /// The previous version of the database schema 130 | /// The version of the new database schema 131 | /// The description of the migration which was applied 132 | public override void UpdateVersion(long oldVersion, long newVersion, string newDescription) 133 | { 134 | this.UpdateVersion(oldVersion, newVersion, newDescription, this.VersionTableConnection, this.VersionTableLockTransaction); 135 | } 136 | 137 | /// 138 | /// Creates and sets , and uses it to lock the VersionInfo table 139 | /// 140 | protected abstract void AcquireVersionTableLock(); 141 | 142 | /// 143 | /// Destroys , thus releasing the VersionInfo table lock 144 | /// 145 | protected abstract void ReleaseVersionTableLock(); 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/Simple.Migrations/DatabaseProvider/MssqlDatabaseProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | using System.Data.Common; 4 | 5 | namespace SimpleMigrations.DatabaseProvider 6 | { 7 | /// 8 | /// Class which can read from / write to a version table in an MSSQL database 9 | /// 10 | /// 11 | /// MSSQL supports advisory locks, so these are used to guard against concurrent migrators. 12 | /// 13 | public class MssqlDatabaseProvider : DatabaseProviderBaseWithAdvisoryLock 14 | { 15 | /// 16 | /// Gets or sets the schema name used to store the version table. 17 | /// 18 | public string SchemaName { get; set; } = "dbo"; 19 | 20 | /// 21 | /// Controls whether or not to try and create the schema if it does not exist. 22 | /// 23 | /// 24 | /// If this is set to false then no schema is created. It is the user's responsibility to create the schema 25 | /// (if necessary) with the correct name and permissions before running the . This may be 26 | /// required if the user which Simple.Migrator is running as does not have the correct permissions to check whether the 27 | /// schema has been created. 28 | /// 29 | public bool CreateSchema { get; set; } = true; 30 | 31 | /// 32 | /// Gets or sets the name of the advisory lock to acquire 33 | /// 34 | public string AdvisoryLockName { get; set; } = "SimpleMigratorExclusiveLock"; 35 | 36 | /// 37 | /// This property has been obsoleted. Use instead 38 | /// 39 | [Obsolete("Use AdvisoryLockName instead")] 40 | public string LockName 41 | { 42 | get => this.AdvisoryLockName; 43 | set => this.AdvisoryLockName = value; 44 | } 45 | 46 | /// 47 | /// Initialises a new instance of the class 48 | /// 49 | /// Connection to use to run migrations. The caller is responsible for closing this. 50 | public MssqlDatabaseProvider(DbConnection connection) 51 | : base(connection) 52 | { 53 | this.MaxDescriptionLength = 256; 54 | } 55 | 56 | /// 57 | /// Acquires an advisory lock using Connection 58 | /// 59 | protected override void AcquireAdvisoryLock() 60 | { 61 | using (var command = this.Connection.CreateCommand()) 62 | { 63 | command.CommandText = $"sp_getapplock @Resource = '{this.AdvisoryLockName}', @LockMode = 'Exclusive', @LockOwner = 'Session', @LockTimeout = '{(int)this.LockTimeout.TotalMilliseconds}'"; 64 | command.CommandTimeout = 0; // The lock will time out by itself 65 | command.ExecuteNonQuery(); 66 | } 67 | } 68 | 69 | /// 70 | /// Releases the advisory lock held on Connection 71 | /// 72 | protected override void ReleaseAdvisoryLock() 73 | { 74 | using (var command = this.Connection.CreateCommand()) 75 | { 76 | command.CommandText = $"sp_releaseapplock @Resource = '{this.AdvisoryLockName}', @LockOwner = 'Session'"; 77 | command.ExecuteNonQuery(); 78 | } 79 | } 80 | 81 | /// 82 | /// Returns SQL to create the schema 83 | /// 84 | /// SQL to create the schema 85 | protected override string GetCreateSchemaTableSql() 86 | { 87 | return this.CreateSchema ? $@"IF NOT EXISTS (select * from sys.schemas WHERE name ='{this.SchemaName}') EXECUTE ('CREATE SCHEMA [{this.SchemaName}]');" : String.Empty; 88 | } 89 | 90 | /// 91 | /// Returns SQL to create the version table 92 | /// 93 | /// SQL to create the version table 94 | protected override string GetCreateVersionTableSql() 95 | { 96 | return $@"IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID('[{this.SchemaName}].[{this.TableName}]') AND type in (N'U')) 97 | BEGIN 98 | CREATE TABLE [{this.SchemaName}].[{this.TableName}]( 99 | [Id] [int] IDENTITY(1,1) PRIMARY KEY NOT NULL, 100 | [Version] [bigint] NOT NULL, 101 | [AppliedOn] [datetime] NOT NULL, 102 | [Description] [nvarchar]({this.MaxDescriptionLength}) NOT NULL, 103 | ) 104 | END;"; 105 | } 106 | 107 | /// 108 | /// Returns SQL to fetch the current version from the version table 109 | /// 110 | /// SQL to fetch the current version from the version table 111 | protected override string GetCurrentVersionSql() 112 | { 113 | return $@"SELECT TOP 1 [Version] FROM [{this.SchemaName}].[{this.TableName}] ORDER BY [Id] desc;"; 114 | } 115 | 116 | /// 117 | /// Returns SQL to update the current version in the version table 118 | /// 119 | /// SQL to update the current version in the version table 120 | protected override string GetSetVersionSql() 121 | { 122 | return $@"INSERT INTO [{this.SchemaName}].[{this.TableName}] ([Version], [AppliedOn], [Description]) VALUES (@Version, GETDATE(), @Description);"; 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/Simple.Migrations/DatabaseProvider/MysqlDatabaseProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data.Common; 3 | 4 | namespace SimpleMigrations.DatabaseProvider 5 | { 6 | /// 7 | /// Class which can read from / write to a version table in an MySQL database 8 | /// 9 | /// 10 | /// MySQL supports advisory locks, so these are used to guard against concurrent migrators. 11 | /// 12 | public class MysqlDatabaseProvider : DatabaseProviderBaseWithAdvisoryLock 13 | { 14 | /// 15 | /// Gets or sets the name of the advisory lock to acquire 16 | /// 17 | public string AdvisoryLockName { get; set; } = "SimpleMigratorExclusiveLock"; 18 | 19 | /// 20 | /// This property has been obsoleted. Use instead 21 | /// 22 | [Obsolete("Use AdvisoryLockName instead")] 23 | public string LockName 24 | { 25 | get => this.AdvisoryLockName; 26 | set => this.AdvisoryLockName = value; 27 | } 28 | 29 | /// 30 | /// Initialises a new instance of the class 31 | /// 32 | /// Connection to use to run migrations. The caller is responsible for closing this. 33 | public MysqlDatabaseProvider(DbConnection connection) 34 | : base(connection) 35 | { 36 | } 37 | 38 | /// 39 | /// Acquires an advisory lock using Connection 40 | /// 41 | protected override void AcquireAdvisoryLock() 42 | { 43 | using (var command = this.Connection.CreateCommand()) 44 | { 45 | command.CommandText = $"SELECT GET_LOCK('{this.AdvisoryLockName}', {(int)this.LockTimeout.TotalSeconds})"; 46 | command.CommandTimeout = 0; // The lock will time out by itself 47 | command.ExecuteNonQuery(); 48 | } 49 | } 50 | 51 | /// 52 | /// Releases the advisory lock held on Connection 53 | /// 54 | protected override void ReleaseAdvisoryLock() 55 | { 56 | using (var command = this.Connection.CreateCommand()) 57 | { 58 | command.CommandText = $"SELECT RELEASE_LOCK('{this.AdvisoryLockName}')"; 59 | command.ExecuteNonQuery(); 60 | } 61 | } 62 | 63 | /// 64 | /// Returns SQL to create the version table 65 | /// 66 | /// SQL to create the version table 67 | protected override string GetCreateVersionTableSql() 68 | { 69 | return $@"CREATE TABLE IF NOT EXISTS {this.TableName} ( 70 | `Id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT, 71 | `Version` BIGINT NOT NULL, 72 | `AppliedOn` DATETIME NOT NULL, 73 | `Description` TEXT NOT NULL 74 | )"; 75 | } 76 | 77 | /// 78 | /// Returns SQL to fetch the current version from the version table 79 | /// 80 | /// SQL to fetch the current version from the version table 81 | protected override string GetCurrentVersionSql() 82 | { 83 | return $@"SELECT `Version` FROM {this.TableName} ORDER BY `Id` DESC LIMIT 1"; 84 | } 85 | 86 | /// 87 | /// Returns SQL to update the current version in the version table 88 | /// 89 | /// SQL to update the current version in the version table 90 | protected override string GetSetVersionSql() 91 | { 92 | return $@"INSERT INTO {this.TableName} (`Version`, `AppliedOn`, `Description`) VALUES (@Version, NOW(), @Description)"; 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/Simple.Migrations/DatabaseProvider/PostgresqlDatabaseProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data.Common; 3 | 4 | namespace SimpleMigrations.DatabaseProvider 5 | { 6 | /// 7 | /// Class which can read from / write to a version table in an PostgreSQL database 8 | /// 9 | /// 10 | /// PostgreSQL supports advisory locks, so these are used to guard against concurrent migrators. 11 | /// 12 | public class PostgresqlDatabaseProvider : DatabaseProviderBaseWithAdvisoryLock 13 | { 14 | /// 15 | /// Gets or sets the schema name used to store the version table. 16 | /// 17 | public string SchemaName { get; set; } = "public"; 18 | 19 | /// 20 | /// Controls whether or not to try and create the schema if it does not exist. 21 | /// 22 | /// 23 | /// If this is set to false then no schema is created. It is the user's responsibility to create the schema 24 | /// (if necessary) with the correct name and permissions before running the . This may be 25 | /// required if the user which Simple.Migrator is running as does not have the correct permissions to check whether the 26 | /// schema has been created. 27 | /// 28 | public bool CreateSchema { get; set; } = true; 29 | 30 | 31 | /// 32 | /// Gets or sets the key to use when acquiring the advisory lock 33 | /// 34 | public int AdvisoryLockKey { get; set; } = 2609878; // Chosen by fair dice roll 35 | 36 | /// 37 | /// Initialises a new instance of the class 38 | /// 39 | /// Connection to use to run migrations. The caller is responsible for closing this. 40 | public PostgresqlDatabaseProvider(DbConnection connection) 41 | : base(connection) 42 | { 43 | } 44 | 45 | /// 46 | /// Acquires an advisory lock using Connection 47 | /// 48 | protected override void AcquireAdvisoryLock() 49 | { 50 | using (var command = this.Connection.CreateCommand()) 51 | { 52 | command.CommandText = $"SELECT pg_advisory_lock({this.AdvisoryLockKey})"; 53 | command.CommandTimeout = (int)this.LockTimeout.TotalSeconds; 54 | command.ExecuteNonQuery(); 55 | } 56 | } 57 | 58 | /// 59 | /// Releases the advisory lock held on Connection 60 | /// 61 | protected override void ReleaseAdvisoryLock() 62 | { 63 | using (var command = this.Connection.CreateCommand()) 64 | { 65 | command.CommandText = $"SELECT pg_advisory_unlock({this.AdvisoryLockKey})"; 66 | command.ExecuteNonQuery(); 67 | } 68 | } 69 | 70 | /// 71 | /// Returns SQL to create the schema 72 | /// 73 | /// SQL to create the schema 74 | protected override string GetCreateSchemaTableSql() 75 | { 76 | return this.CreateSchema ? $@"CREATE SCHEMA IF NOT EXISTS {this.SchemaName}" : String.Empty; 77 | } 78 | 79 | /// 80 | /// Returns SQL to create the version table 81 | /// 82 | /// SQL to create the version table 83 | protected override string GetCreateVersionTableSql() 84 | { 85 | return $@"CREATE TABLE IF NOT EXISTS {this.SchemaName}.{this.TableName} ( 86 | Id SERIAL PRIMARY KEY, 87 | Version bigint NOT NULL, 88 | AppliedOn timestamp with time zone, 89 | Description text NOT NULL 90 | )"; 91 | } 92 | 93 | /// 94 | /// Returns SQL to fetch the current version from the version table 95 | /// 96 | /// SQL to fetch the current version from the version table 97 | protected override string GetCurrentVersionSql() 98 | { 99 | return $@"SELECT Version FROM {this.SchemaName}.{this.TableName} ORDER BY Id DESC LIMIT 1"; 100 | } 101 | 102 | /// 103 | /// Returns SQL to update the current version in the version table 104 | /// 105 | /// SQL to update the current version in the version table 106 | protected override string GetSetVersionSql() 107 | { 108 | return $@"INSERT INTO {this.SchemaName}.{this.TableName} (Version, AppliedOn, Description) VALUES (@Version, CURRENT_TIMESTAMP, @Description)"; 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/Simple.Migrations/DatabaseProvider/SqliteDatabaseProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | using System.Data.Common; 4 | 5 | namespace SimpleMigrations.DatabaseProvider 6 | { 7 | /// 8 | /// Class which can read from / write to a version table in an SQLite database 9 | /// 10 | /// 11 | /// SQLite does not support advisory locks, and its transaction model means that we cannot use a transaction on the 12 | /// VersionInfo table to guard against concurrent migrators. Therefore this database does not provide support for 13 | /// concurrent migrators. 14 | /// 15 | public class SqliteDatabaseProvider : DatabaseProviderBaseWithAdvisoryLock 16 | { 17 | /// 18 | /// Initialises a new instance of the class 19 | /// 20 | /// Connection to use to run migrations. The caller is responsible for closing this. 21 | public SqliteDatabaseProvider(DbConnection connection) 22 | : base(connection) 23 | { 24 | } 25 | 26 | /// 27 | /// No-op: SQLite does not support advisory locks 28 | /// 29 | protected override void AcquireAdvisoryLock() { } 30 | 31 | /// 32 | /// No-op: SQLite does not support advisory locks 33 | /// 34 | protected override void ReleaseAdvisoryLock() { } 35 | 36 | /// 37 | /// Returns SQL to create the version table 38 | /// 39 | /// SQL to create the version table 40 | protected override string GetCreateVersionTableSql() 41 | { 42 | return $@"CREATE TABLE IF NOT EXISTS {this.TableName} ( 43 | Id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, 44 | Version INTEGER NOT NULL, 45 | AppliedOn DATETIME NOT NULL, 46 | Description TEXT NOT NULL 47 | )"; 48 | } 49 | 50 | /// 51 | /// Returns SQL to fetch the current version from the version table 52 | /// 53 | /// SQL to fetch the current version from the version table 54 | protected override string GetCurrentVersionSql() 55 | { 56 | return $@"SELECT Version FROM {this.TableName} ORDER BY Id DESC LIMIT 1"; 57 | } 58 | 59 | /// 60 | /// Returns SQL to update the current version in the version table 61 | /// 62 | /// SQL to update the current version in the version table 63 | protected override string GetSetVersionSql() 64 | { 65 | return $@"INSERT INTO {this.TableName} (Version, AppliedOn, Description) VALUES (@Version, datetime('now', 'localtime'), @Description)"; 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Simple.Migrations/IDatabaseProvider.cs: -------------------------------------------------------------------------------- 1 | using SimpleMigrations.DatabaseProvider; 2 | using System; 3 | 4 | namespace SimpleMigrations 5 | { 6 | /// 7 | /// Interface representing database-type-specific operations which needs to be performed 8 | /// 9 | /// 10 | /// Methods on this interface are called according to a strict sequence. 11 | /// 12 | /// When is called: 13 | /// 1. is invoked 14 | /// 15 | /// 16 | /// When or 17 | /// is called: 18 | /// 1. is called. 19 | /// 2. is called. 20 | /// 3. is called (potentially multiple times) 21 | /// 4. is called. 22 | /// 5. is called. 23 | /// 24 | /// Different databases require different locking strategies to guard against concurrent migrators. 25 | /// 26 | /// Databases which support advisory locks typically have a single connection, and use this for obtaining 27 | /// the advisory lock, running migrations, and updating the VersionInfo table. The advisory lock is obtained 28 | /// when creating the VersionInfo table also. 29 | /// 30 | /// Databases which do not support advisory locks typically have a connection factory. Inside 31 | /// they will create two connections. One creates a transaction and uses it to 32 | /// lock the VersionInfo table, and also to update the VersionInfo table, while the other is used to run migrations. 33 | /// 34 | /// The subclasses and 35 | /// encapsulate these concepts. 36 | /// 37 | /// Type of database connection 38 | public interface IDatabaseProvider 39 | { 40 | /// 41 | /// Called when or 42 | /// is invoked, before any migrations are run. This should create connections and/or transactions and/or locks if necessary, 43 | /// and return the connection for the migrations to use. 44 | /// 45 | /// Connection for the migrations to use 46 | TConnection BeginOperation(); 47 | 48 | /// 49 | /// Cleans up any connections and/or transactions and/or locks created by 50 | /// 51 | /// 52 | /// This is always paired with a call to : it is called exactly once for every time that 53 | /// is called. 54 | /// 55 | void EndOperation(); 56 | 57 | /// 58 | /// Ensures that the version table is created, and returns the current version. 59 | /// 60 | /// 61 | /// This is not surrounded by calls to or , so 62 | /// it should do whatever locking is appropriate to guard against concurrent migrators. 63 | /// 64 | /// If the version table is empty, this should return 0. 65 | /// 66 | /// The current version, or 0 67 | long EnsurePrerequisitesCreatedAndGetCurrentVersion(); 68 | 69 | /// 70 | /// Fetch the current database schema version, or 0. 71 | /// 72 | /// 73 | /// This method is always invoked after a call to , but before a call to 74 | /// . Therefore it may use a connection created by . 75 | /// 76 | /// If this databases uses locking on the VersionInfo table to guard against concurrent migrators, this 77 | /// method should use the connection that lock was acquired on. 78 | /// 79 | /// The current database schema version, or 0 80 | long GetCurrentVersion(); 81 | 82 | /// 83 | /// Update the VersionInfo table to indicate that the given migration was successfully applied. 84 | /// 85 | /// 86 | /// This is always invoked after a call to but before a call to , 87 | /// Therefore it may use a connection created by . 88 | /// 89 | /// The previous version of the database schema 90 | /// The version of the new database schema 91 | /// The description of the migration which was applied 92 | void UpdateVersion(long oldVersion, long newVersion, string newDescription); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/Simple.Migrations/ILogger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SimpleMigrations 4 | { 5 | /// 6 | /// A logger, which can log migration progress and output 7 | /// 8 | public interface ILogger : IMigrationLogger 9 | { 10 | /// 11 | /// Invoked when a sequence of migrations is started 12 | /// 13 | /// Migration being migrated from 14 | /// Migration being migrated to 15 | void BeginSequence(MigrationData from, MigrationData to); 16 | 17 | /// 18 | /// Invoked when a sequence of migrations is completed successfully 19 | /// 20 | /// Migration which was migrated from 21 | /// Migration which was migrated to 22 | void EndSequence(MigrationData from, MigrationData to); 23 | 24 | /// 25 | /// Invoked when a sequence of migrations fails with an error 26 | /// 27 | /// Exception which was encountered 28 | /// Migration which was migrated from 29 | /// Last successful migration which was applied 30 | void EndSequenceWithError(Exception exception, MigrationData from, MigrationData currentVersion); 31 | 32 | /// 33 | /// Invoked when an individual migration is started 34 | /// 35 | /// Migration being started 36 | /// Direction of the migration 37 | void BeginMigration(MigrationData migration, MigrationDirection direction); 38 | 39 | /// 40 | /// Invoked when an individual migration is completed successfully 41 | /// 42 | /// Migration which completed 43 | /// Direction of the migration 44 | void EndMigration(MigrationData migration, MigrationDirection direction); 45 | 46 | /// 47 | /// Invoked when an individual migration fails with an error 48 | /// 49 | /// Exception which was encountered 50 | /// Migration which failed 51 | /// Direction of the migration 52 | void EndMigrationWithError(Exception exception, MigrationData migration, MigrationDirection direction); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Simple.Migrations/IMigration.cs: -------------------------------------------------------------------------------- 1 | namespace SimpleMigrations 2 | { 3 | /// 4 | /// Interface which must be implemented by all migrations, although you probably want to derive from instead 5 | /// 6 | /// Type of database connection which this migration will use 7 | public interface IMigration 8 | { 9 | /// 10 | /// Run the migration in the given direction, using the given connection and logger 11 | /// 12 | /// 13 | /// The migration should create a transaction if appropriate 14 | /// 15 | /// Data used by the migration 16 | void RunMigration(MigrationRunData data); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Simple.Migrations/IMigrationLogger.cs: -------------------------------------------------------------------------------- 1 | namespace SimpleMigrations 2 | { 3 | /// 4 | /// Logger used by migrations to log things 5 | /// 6 | public interface IMigrationLogger 7 | { 8 | /// 9 | /// Invoked when another informative message should be logged 10 | /// 11 | /// Message to be logged 12 | void Info(string message); 13 | 14 | /// 15 | /// Invoked when SQL being executed should be logged 16 | /// 17 | /// SQL to log 18 | void LogSql(string sql); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Simple.Migrations/IMigrationProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace SimpleMigrations 4 | { 5 | /// 6 | /// Defines a class which loads the list of migration metadata 7 | /// 8 | /// 9 | /// By default this is implemented by 10 | /// 11 | public interface IMigrationProvider 12 | { 13 | /// 14 | /// Load all migration info. These can be in any order 15 | /// 16 | /// All migration info 17 | IEnumerable LoadMigrations(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Simple.Migrations/ISimpleMigrator.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace SimpleMigrations 4 | { 5 | /// 6 | /// Interface defining how to interact with a SimpleMigrator 7 | /// 8 | public interface ISimpleMigrator 9 | { 10 | /// 11 | /// Gets and sets the logger to use. May be null 12 | /// 13 | ILogger Logger { get; set; } 14 | 15 | /// 16 | /// Gets the currently-applied migration 17 | /// 18 | MigrationData CurrentMigration { get; } 19 | 20 | /// 21 | /// Gets the latest available migration 22 | /// 23 | MigrationData LatestMigration { get; } 24 | 25 | /// 26 | /// Gets all available migrations 27 | /// 28 | IReadOnlyList Migrations { get; } 29 | 30 | /// 31 | /// Load all available migrations, and the current state of the database 32 | /// 33 | void Load(); 34 | 35 | /// 36 | /// Migrate up to the latest version 37 | /// 38 | void MigrateToLatest(); 39 | 40 | /// 41 | /// Migrate to a specific version 42 | /// 43 | /// Version to migrate to 44 | void MigrateTo(long newVersion); 45 | 46 | /// 47 | /// Pretend that the database is at the given version, without running any migrations. 48 | /// This is useful for introducing SimpleMigrations to an existing database. 49 | /// 50 | /// Version to introduce 51 | void Baseline(long version); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Simple.Migrations/Migration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | using System.Data.Common; 4 | 5 | namespace SimpleMigrations 6 | { 7 | /// 8 | /// Base class, intended to be used by all migrations (although you may implement directly if you wish). 9 | /// Migrations MUST apply the attribute 10 | /// 11 | public abstract class Migration : IMigration 12 | { 13 | /// 14 | /// Gets or sets a value indicating whether calls to should be run inside of a transaction. 15 | /// 16 | /// 17 | /// If this is false, will not be set. 18 | /// 19 | protected virtual bool UseTransaction { get; set; } = true; 20 | 21 | /// 22 | /// Gets or sets the database to be used by this migration 23 | /// 24 | protected DbConnection Connection { get; private set; } 25 | 26 | /// 27 | /// Gets or sets the logger to be used by this migration 28 | /// 29 | protected IMigrationLogger Logger { get; private set; } 30 | 31 | /// 32 | /// Gets the transaction to use when running comments. 33 | /// 34 | /// 35 | /// This is set only if is true. 36 | /// 37 | protected DbTransaction Transaction { get; private set; } 38 | 39 | // Up and Down should really be 'protected', but in the name of backwards compatibility... 40 | 41 | /// 42 | /// Invoked when this migration should migrate up 43 | /// 44 | protected abstract void Up(); 45 | 46 | /// 47 | /// Invoked when this migration should migrate down 48 | /// 49 | protected abstract void Down(); 50 | 51 | /// 52 | /// Execute and log an SQL query (which returns no data) 53 | /// 54 | /// SQL to execute 55 | /// The command timeout to use (in seconds), or null to use the default from your ADO.NET provider 56 | protected virtual void Execute(string sql, int? commandTimeout = null) 57 | { 58 | if (this.Logger == null) 59 | throw new InvalidOperationException("this.Logger has not yet been set. This should have been set by Execute(DbConnection, IMigrationLogger, MigrationDirection)"); 60 | 61 | this.Logger.LogSql(sql); 62 | 63 | using (var command = this.CreateCommand(sql, commandTimeout)) 64 | { 65 | command.ExecuteNonQuery(); 66 | } 67 | } 68 | 69 | /// 70 | /// Creates a , which is used by to execute SQL commands 71 | /// 72 | /// 73 | /// You can use this method to create commands if you want to set parameters, or access the result of a query. 74 | /// Override this method if you want to customise how commands are created, e.g. by setting other properties on . 75 | /// 76 | /// SQL to execute 77 | /// The command timeout to use (in seconds), or null to use the default from your ADO.NET provider 78 | /// 79 | /// A which is configured with the , , 80 | /// and properties set. 81 | /// 82 | protected virtual DbCommand CreateCommand(string sql, int? commandTimeout) 83 | { 84 | if (this.Connection == null) 85 | throw new InvalidOperationException("this.Connection has not yet been set. This should have been set by Execute(DbConnection, IMigrationLogger, MigrationDirection)"); 86 | 87 | var command = this.Connection.CreateCommand(); 88 | command.CommandText = sql; 89 | command.Transaction = this.Transaction; 90 | 91 | if (commandTimeout != null) 92 | command.CommandTimeout = commandTimeout.Value; 93 | 94 | return command; 95 | } 96 | 97 | void IMigration.RunMigration(MigrationRunData data) 98 | { 99 | this.Connection = data.Connection; 100 | this.Logger = data.Logger; 101 | 102 | if (this.UseTransaction) 103 | { 104 | using (this.Transaction = this.Connection.BeginTransaction(IsolationLevel.Serializable)) 105 | { 106 | try 107 | { 108 | if (data.Direction == MigrationDirection.Up) 109 | this.Up(); 110 | else 111 | this.Down(); 112 | 113 | this.Transaction.Commit(); 114 | } 115 | catch 116 | { 117 | this.Transaction?.Rollback(); 118 | throw; 119 | } 120 | finally 121 | { 122 | this.Transaction = null; 123 | } 124 | } 125 | } 126 | else 127 | { 128 | if (data.Direction == MigrationDirection.Up) 129 | this.Up(); 130 | else 131 | this.Down(); 132 | } 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/Simple.Migrations/MigrationAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SimpleMigrations 4 | { 5 | /// 6 | /// [Migration(version)] attribute which must be applied to all migrations 7 | /// 8 | [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] 9 | public sealed class MigrationAttribute : Attribute 10 | { 11 | /// 12 | /// Version of this migration 13 | /// 14 | public long Version { get; set; } 15 | 16 | /// 17 | /// Gets or sets the optional description of this migration 18 | /// 19 | public string Description { get; set; } 20 | 21 | /// 22 | /// Instantiates a new instance of the class 23 | /// 24 | /// Version of this migration 25 | /// Optional description of this migration 26 | public MigrationAttribute(long version, string description = null) 27 | { 28 | this.Version = version; 29 | this.Description = description; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Simple.Migrations/MigrationData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | 4 | namespace SimpleMigrations 5 | { 6 | /// 7 | /// Class representing data about a migration 8 | /// 9 | public class MigrationData 10 | { 11 | internal static MigrationData EmptySchema { get; } = new MigrationData(); 12 | 13 | /// 14 | /// Version of this migration 15 | /// 16 | public long Version { get; } 17 | 18 | /// 19 | /// Description of this migration 20 | /// 21 | public string Description { get; } 22 | 23 | /// 24 | /// Type of class implementing this migration 25 | /// 26 | public TypeInfo TypeInfo { get; } 27 | 28 | /// 29 | /// Name of the migration, including the type name and description 30 | /// 31 | public string FullName { get; } 32 | 33 | /// 34 | /// Initialises a new instance of the class 35 | /// 36 | /// Version of this migration 37 | /// Description of this migration. May be null 38 | /// Type of class implementing this migration 39 | public MigrationData(long version, string description, TypeInfo typeInfo) 40 | { 41 | // 'version' is verified to be >0 in SimpleMigrator 42 | if (typeInfo == null) 43 | throw new ArgumentNullException(nameof(typeInfo)); 44 | 45 | this.Version = version; 46 | this.Description = description; 47 | this.TypeInfo = typeInfo; 48 | 49 | var descriptionPart = String.IsNullOrWhiteSpace(this.Description) ? "" : $" ({this.Description})"; 50 | this.FullName = this.TypeInfo.Name + descriptionPart; 51 | } 52 | 53 | /// 54 | /// Creates the empty scheme migration 55 | /// 56 | private MigrationData() 57 | { 58 | this.Version = 0; 59 | this.Description = "Empty Schema"; 60 | this.FullName = "Empty Schema"; 61 | this.TypeInfo = null; 62 | } 63 | 64 | /// 65 | /// Returns a string representation of the object 66 | /// 67 | /// A string representation of the object 68 | public override string ToString() 69 | { 70 | return $"<{nameof(MigrationData)} Version={this.Version} Description={this.Description} FullName={this.FullName} TypeInfo={this.TypeInfo}>"; 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/Simple.Migrations/MigrationDirection.cs: -------------------------------------------------------------------------------- 1 | namespace SimpleMigrations 2 | { 3 | /// 4 | /// Represents the direction of a migration 5 | /// 6 | public enum MigrationDirection 7 | { 8 | /// 9 | /// Migration is going up: the migration being applied is newer than the current version 10 | /// 11 | Up, 12 | 13 | /// 14 | /// Migration is going down: the migration being applied is older than the current version 15 | /// 16 | Down 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Simple.Migrations/MigrationException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SimpleMigrations 4 | { 5 | /// 6 | /// An exception relating to migrations occurred 7 | /// 8 | public class MigrationException : Exception 9 | { 10 | /// 11 | /// Instantiates a new instance of the class 12 | /// 13 | public MigrationException() 14 | { 15 | } 16 | 17 | /// 18 | /// Instantiates a new instance of the class 19 | /// 20 | /// Message to use 21 | public MigrationException(string message) 22 | : base(message) 23 | { 24 | } 25 | 26 | /// 27 | /// Instantiates a new instance of the class 28 | /// 29 | /// Message to use 30 | /// Inner exception to use 31 | public MigrationException(string message, Exception innerException) 32 | : base(message, innerException) 33 | { 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /src/Simple.Migrations/MigrationLoadFailedException.cs: -------------------------------------------------------------------------------- 1 | namespace SimpleMigrations 2 | { 3 | /// 4 | /// Thrown if there was a problem loading the migrations 5 | /// 6 | public class MigrationLoadFailedException : MigrationException 7 | { 8 | /// 9 | /// Initialises a new instance of the class 10 | /// 11 | /// Message to use 12 | public MigrationLoadFailedException(string message) 13 | : base(message) 14 | { 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Simple.Migrations/MigrationNotFoundException.cs: -------------------------------------------------------------------------------- 1 | namespace SimpleMigrations 2 | { 3 | /// 4 | /// Exception thrown when the requested migration could not be found 5 | /// 6 | public class MigrationNotFoundException : MigrationException 7 | { 8 | /// 9 | /// The version of the migration which was requested but not found 10 | /// 11 | public long Version => (long)this.Data[nameof(this.Version)]; 12 | 13 | /// 14 | /// Initialises a new instance of the class 15 | /// 16 | /// Version of the migration which was requested but not found 17 | public MigrationNotFoundException(long version) 18 | : base($"Could not find migration with version {version}") 19 | { 20 | this.Data[nameof(this.Version)] = version; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Simple.Migrations/MigrationRunData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SimpleMigrations 4 | { 5 | /// 6 | /// Data passed to 7 | /// 8 | /// 9 | /// This is split out into a separate class for backwards compatibility 10 | /// 11 | /// Type of database connection which this migration will use 12 | public class MigrationRunData 13 | { 14 | /// 15 | /// Connection to use to run the migration 16 | /// 17 | public TConnection Connection { get; } 18 | 19 | /// 20 | /// Logger to use to log SQL statements run, and other messages 21 | /// 22 | public IMigrationLogger Logger { get; } 23 | 24 | /// 25 | /// Direction to run the migration in 26 | /// 27 | public MigrationDirection Direction { get; } 28 | 29 | /// 30 | /// Initialises a new instance of the class 31 | /// 32 | /// Connection to use to run the migration 33 | /// Logger to use to log SQL statements run, and other messages 34 | /// Direction to run the migration in 35 | public MigrationRunData(TConnection connection, IMigrationLogger logger, MigrationDirection direction) 36 | { 37 | if (connection == null) 38 | throw new ArgumentNullException(nameof(connection)); 39 | if (logger == null) 40 | throw new ArgumentNullException(nameof(logger)); 41 | 42 | this.Connection = connection; 43 | this.Logger = logger; 44 | this.Direction = direction; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Simple.Migrations/MissingMigrationException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SimpleMigrations 4 | { 5 | /// 6 | /// Exception thrown when migration data is missing 7 | /// 8 | /// 9 | /// This happens if SimpleMigrator finds that the database is at a particular version, but can't find 10 | /// a migration with that version. 11 | /// 12 | public class MissingMigrationException : MigrationException 13 | { 14 | /// 15 | /// Gets the version that migration data could not be found for 16 | /// 17 | public long MissingVersion => (long)this.Data[nameof(this.MissingVersion)]; 18 | 19 | /// 20 | /// Instantiates a new instance of the class with the given missing version 21 | /// 22 | /// Version that migration data could not be found for 23 | public MissingMigrationException(long missingVersion) 24 | : base($"Unable to find a migration with the version {missingVersion}") 25 | { 26 | this.Data[nameof(this.MissingVersion)] = missingVersion; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Simple.Migrations/NullLogger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SimpleMigrations 4 | { 5 | internal class NullLogger : ILogger 6 | { 7 | public static NullLogger Instance { get; } = new NullLogger(); 8 | 9 | public void BeginSequence(MigrationData from, MigrationData to) { } 10 | public void EndSequence(MigrationData from, MigrationData to) { } 11 | public void EndSequenceWithError(Exception exception, MigrationData from, MigrationData currentVersion) { } 12 | 13 | public void BeginMigration(MigrationData migration, MigrationDirection direction) { } 14 | public void EndMigration(MigrationData migration, MigrationDirection direction) { } 15 | public void EndMigrationWithError(Exception exception, MigrationData migration, MigrationDirection direction) { } 16 | 17 | public void Info(string message) { } 18 | public void LogSql(string message) { } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Simple.Migrations/Simple.Migrations.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 0.0.0 4 | netstandard1.2;netstandard1.3;netstandard2.0;net45 5 | true 6 | Simple.Migrations 7 | Simple.Migrations 8 | Library 9 | Simple.Migrations 10 | SQL;Migration 11 | Copyright 2015-2017 Antony Male 12 | https://raw.githubusercontent.com/canton7/Simple.Migrations/master/icon.png 13 | https://github.com/canton7/Simple.Migrations 14 | http://github.com/canton7/Simple.Migrations/blob/master/LICENSE.txt 15 | git 16 | https://github.com/canton7/Simple.Migrations 17 | Simple but flexible migrations library 18 | Antony Male 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /src/Simple.Migrations/SimpleMigrations.ruleset: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /src/Simple.Migrations/SimpleMigrator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | using System.Data.Common; 4 | using System.Reflection; 5 | 6 | namespace SimpleMigrations 7 | { 8 | /// 9 | /// Migrator which uses connections 10 | /// 11 | public class SimpleMigrator : SimpleMigrator> 12 | { 13 | /// 14 | /// Instantiates a new instance of the class 15 | /// 16 | /// Migration provider to use to find migration classes 17 | /// implementation to use 18 | /// Logger to use to log progress 19 | public SimpleMigrator( 20 | IMigrationProvider migrationProvider, 21 | IDatabaseProvider databaseProvider, 22 | ILogger logger = null) 23 | : base(migrationProvider, databaseProvider, logger) 24 | { 25 | } 26 | 27 | /// 28 | /// Instantiates a new instance of the class 29 | /// 30 | /// Assembly to search for migrations 31 | /// implementation to use 32 | /// Logger to use to log progress 33 | public SimpleMigrator( 34 | Assembly migrationsAssembly, 35 | IDatabaseProvider databaseProvider, 36 | ILogger logger = null) 37 | : base(migrationsAssembly, databaseProvider, logger) 38 | { 39 | } 40 | } 41 | } 42 | --------------------------------------------------------------------------------