@@ -411,14 +411,95 @@ title TEXT NOT NULL
411411 Assert . AreEqual ( "0001_CreateMovies" , GetAppliedMigrationId ( connection , 1 ) ) ;
412412 }
413413
414+ [ TestMethod ]
415+ public void Migrate_ExistingDbContextCommit_PersistsMigrationsAndHistory ( )
416+ {
417+ const string version = "1.2.3" ;
418+
419+ var migration = new DelegateMigration ( "0001_CreateMovies" , static context => context . ExecuteNonQuery (
420+ """
421+ CREATE TABLE movies (
422+ id INTEGER PRIMARY KEY NOT NULL,
423+ title TEXT NOT NULL
424+ );
425+ """ ) ) ;
426+
427+ SQLiteDbMigration [ ] migrations =
428+ [
429+ migration
430+ ] ;
431+
432+ using ( var connection = CreateConnection ( ) )
433+ {
434+ connection . AcquireLock ( ( ) =>
435+ {
436+ using var dbContext = new SQLiteDbContext ( connection ) ;
437+
438+ migrations . Migrate ( dbContext , version , CancellationToken . None ) ;
439+
440+ Assert . IsTrue ( connection . TableExists ( "movies" , CancellationToken . None ) ) ;
441+ Assert . IsTrue ( connection . TableExists ( "__nuext_migrations" , CancellationToken . None ) ) ;
442+
443+ dbContext . Commit ( ) ;
444+ } , CancellationToken . None ) ;
445+ }
446+
447+ Assert . AreEqual ( 1 , migration . ExecutionCount ) ;
448+
449+ using var verificationConnection = CreateConnection ( ) ;
450+ Assert . IsTrue ( verificationConnection . TableExists ( "movies" , CancellationToken . None ) ) ;
451+ Assert . IsTrue ( verificationConnection . TableExists ( "__nuext_migrations" , CancellationToken . None ) ) ;
452+ Assert . AreEqual ( 1L , GetAppliedMigrationCount ( verificationConnection ) ) ;
453+ Assert . AreEqual ( "0001_CreateMovies" , GetAppliedMigrationId ( verificationConnection , 1 ) ) ;
454+ Assert . AreEqual ( version , GetAppliedMigrationVersion ( verificationConnection , 1 ) ) ;
455+ }
456+
457+ [ TestMethod ]
458+ public void Migrate_ExistingDbContextRollback_RollsBackMigrationsAndHistory ( )
459+ {
460+ var migration = new DelegateMigration ( "0001_CreateMovies" , static context => context . ExecuteNonQuery (
461+ """
462+ CREATE TABLE movies (
463+ id INTEGER PRIMARY KEY NOT NULL,
464+ title TEXT NOT NULL
465+ );
466+ """ ) ) ;
467+
468+ SQLiteDbMigration [ ] migrations =
469+ [
470+ migration
471+ ] ;
472+
473+ using ( var connection = CreateConnection ( ) )
474+ {
475+ connection . AcquireLock ( ( ) =>
476+ {
477+ using var dbContext = new SQLiteDbContext ( connection ) ;
478+
479+ migrations . Migrate ( dbContext , CancellationToken . None ) ;
480+
481+ Assert . IsTrue ( connection . TableExists ( "movies" , CancellationToken . None ) ) ;
482+ Assert . IsTrue ( connection . TableExists ( "__nuext_migrations" , CancellationToken . None ) ) ;
483+
484+ dbContext . Rollback ( ) ;
485+ } , CancellationToken . None ) ;
486+ }
487+
488+ Assert . AreEqual ( 1 , migration . ExecutionCount ) ;
489+
490+ using var verificationConnection = CreateConnection ( ) ;
491+ Assert . IsFalse ( verificationConnection . TableExists ( "movies" , CancellationToken . None ) ) ;
492+ Assert . IsFalse ( verificationConnection . TableExists ( "__nuext_migrations" , CancellationToken . None ) ) ;
493+ }
494+
414495 private SQLiteDbConnection CreateConnection ( )
415496 {
416497 return new SQLiteDbConnection ( _connectionString ) ;
417498 }
418499
419500 private static long GetAppliedMigrationCount ( SQLiteDbConnection connection )
420501 {
421- object ? result = connection . ExecuteScalar ( "SELECT COUNT(*) FROM __nuext_migrations;" ) ;
502+ object ? result = connection . ExecuteScalar ( "SELECT COUNT(*) FROM __nuext_migrations;" , CommandBehavior . Default , CancellationToken . None ) ;
422503 Assert . IsNotNull ( result ) ;
423504 return Convert . ToInt64 ( result ) ;
424505 }
@@ -438,22 +519,24 @@ private static void CreateMigrationHistory(SQLiteDbConnection connection)
438519 CREATE TABLE __nuext_migrations (
439520 sequence INTEGER PRIMARY KEY,
440521 migration_id TEXT NOT NULL UNIQUE CHECK(migration_id <> ''),
522+ version TEXT,
441523 applied_at INTEGER NOT NULL
442524 );
443525 """ ) ;
444526 }
445527
446- private static void AddAppliedMigration ( SQLiteDbConnection connection , string migrationId , long sequence )
528+ private static void AddAppliedMigration ( SQLiteDbConnection connection , string migrationId , long sequence , string ? version = null )
447529 {
448530 connection . ExecuteNonQuery (
449531 """
450- INSERT INTO __nuext_migrations (sequence, migration_id, applied_at)
451- VALUES (@sequence, @migration_id, @applied_at);
532+ INSERT INTO __nuext_migrations (sequence, migration_id, version, applied_at)
533+ VALUES (@sequence, @migration_id, @version, @ applied_at);
452534 """ ,
453535 CommandBehavior . Default ,
454536 default ,
455537 DbType . Int64 . CreateInputParam ( "@sequence" , sequence ) ,
456538 DbType . String . CreateInputParam ( "@migration_id" , migrationId ) ,
539+ DbType . String . CreateInputParam ( "@version" , version ) ,
457540 DbType . Int64 . CreateInputParam ( "@applied_at" , DateTimeOffset . UtcNow . ToUnixTimeMilliseconds ( ) ) ) ;
458541 }
459542
@@ -473,6 +556,21 @@ FROM __nuext_migrations
473556 return Convert . ToString ( result ) ! ;
474557 }
475558
559+ private static string ? GetAppliedMigrationVersion ( SQLiteDbConnection connection , long sequence )
560+ {
561+ object ? result = connection . ExecuteScalar (
562+ """
563+ SELECT version
564+ FROM __nuext_migrations
565+ WHERE sequence = @sequence;
566+ """ ,
567+ CommandBehavior . Default ,
568+ default ,
569+ DbType . Int64 . CreateInputParam ( "@sequence" , sequence ) ) ;
570+
571+ return result is null or DBNull ? null : Convert . ToString ( result ) ;
572+ }
573+
476574 private sealed class DelegateMigration : SQLiteDbMigration
477575 {
478576 private readonly Action < SQLiteDbMigrationContext > _up ;
0 commit comments