diff --git a/dotnet-efcore/RevertMigration/Migrations/20231209224404_Init.Designer.cs b/dotnet-efcore/RevertMigration/Migrations/20231209224404_Init.Designer.cs index f537fdb045..2b064621ce 100644 --- a/dotnet-efcore/RevertMigration/Migrations/20231209224404_Init.Designer.cs +++ b/dotnet-efcore/RevertMigration/Migrations/20231209224404_Init.Designer.cs @@ -16,7 +16,7 @@ partial class Init protected override void BuildTargetModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "8.0.0"); + modelBuilder.HasAnnotation("ProductVersion", "10.0.12"); modelBuilder.Entity("Airplane", b => { @@ -24,20 +24,19 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); - b.Property("HangarId") + b.Property("HangarId") .HasColumnType("INTEGER"); - b.Property("MaxAirSpeed") + b.Property("MaxAirSpeed") .HasColumnType("REAL"); - b.Property("NumberOfEngines") + b.Property("NumberOfEngines") .HasColumnType("INTEGER"); - b.Property("RunsOnJetFuel") + b.Property("RunsOnJetFuel") .HasColumnType("INTEGER"); b.Property("TailNumber") - .IsRequired() .HasColumnType("TEXT"); b.HasKey("Id"); @@ -54,7 +53,6 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .HasColumnType("INTEGER"); b.Property("HangarNumber") - .IsRequired() .HasColumnType("TEXT"); b.HasKey("Id"); @@ -66,9 +64,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) { b.HasOne("Hangar", "Hangar") .WithMany("Airplanes") - .HasForeignKey("HangarId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); + .HasForeignKey("HangarId"); b.Navigation("Hangar"); }); diff --git a/dotnet-efcore/RevertMigration/Migrations/20231209224404_Init.cs b/dotnet-efcore/RevertMigration/Migrations/20231209224404_Init.cs index 1a214cb86f..0dd8f05d60 100644 --- a/dotnet-efcore/RevertMigration/Migrations/20231209224404_Init.cs +++ b/dotnet-efcore/RevertMigration/Migrations/20231209224404_Init.cs @@ -16,7 +16,7 @@ protected override void Up(MigrationBuilder migrationBuilder) { Id = table.Column(type: "INTEGER", nullable: false) .Annotation("Sqlite:Autoincrement", true), - HangarNumber = table.Column(type: "TEXT", nullable: false) + HangarNumber = table.Column(type: "TEXT", nullable: true) }, constraints: table => { @@ -29,11 +29,11 @@ protected override void Up(MigrationBuilder migrationBuilder) { Id = table.Column(type: "INTEGER", nullable: false) .Annotation("Sqlite:Autoincrement", true), - TailNumber = table.Column(type: "TEXT", nullable: false), - NumberOfEngines = table.Column(type: "INTEGER", nullable: false), - MaxAirSpeed = table.Column(type: "REAL", nullable: false), - RunsOnJetFuel = table.Column(type: "INTEGER", nullable: false), - HangarId = table.Column(type: "INTEGER", nullable: false) + TailNumber = table.Column(type: "TEXT", nullable: true), + NumberOfEngines = table.Column(type: "INTEGER", nullable: true), + MaxAirSpeed = table.Column(type: "REAL", nullable: true), + RunsOnJetFuel = table.Column(type: "INTEGER", nullable: true), + HangarId = table.Column(type: "INTEGER", nullable: true) }, constraints: table => { @@ -42,8 +42,7 @@ protected override void Up(MigrationBuilder migrationBuilder) name: "FK_Airplanes_Hangars_HangarId", column: x => x.HangarId, principalTable: "Hangars", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); + principalColumn: "Id"); }); migrationBuilder.CreateIndex( diff --git a/dotnet-efcore/RevertMigration/Migrations/20231209231131_Hangar_HasDoors.Designer.cs b/dotnet-efcore/RevertMigration/Migrations/20231209231131_Hangar_HasDoors.Designer.cs new file mode 100644 index 0000000000..52d148d313 --- /dev/null +++ b/dotnet-efcore/RevertMigration/Migrations/20231209231131_Hangar_HasDoors.Designer.cs @@ -0,0 +1,82 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace RevertMigration.Migrations +{ + [DbContext(typeof(AirportDbContext))] + [Migration("20231209231131_Hangar_HasDoors")] + partial class Hangar_HasDoors + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.0.12"); + + modelBuilder.Entity("Airplane", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("HangarId") + .HasColumnType("INTEGER"); + + b.Property("MaxAirSpeed") + .HasColumnType("REAL"); + + b.Property("NumberOfEngines") + .HasColumnType("INTEGER"); + + b.Property("RunsOnJetFuel") + .HasColumnType("INTEGER"); + + b.Property("TailNumber") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("HangarId"); + + b.ToTable("Airplanes"); + }); + + modelBuilder.Entity("Hangar", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("HangarNumber") + .HasColumnType("TEXT"); + + b.Property("HasDoors") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Hangars"); + }); + + modelBuilder.Entity("Airplane", b => + { + b.HasOne("Hangar", "Hangar") + .WithMany("Airplanes") + .HasForeignKey("HangarId"); + + b.Navigation("Hangar"); + }); + + modelBuilder.Entity("Hangar", b => + { + b.Navigation("Airplanes"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/dotnet-efcore/RevertMigration/Migrations/20231209231131_Hangar_HasDoors.cs b/dotnet-efcore/RevertMigration/Migrations/20231209231131_Hangar_HasDoors.cs new file mode 100644 index 0000000000..ec92717e39 --- /dev/null +++ b/dotnet-efcore/RevertMigration/Migrations/20231209231131_Hangar_HasDoors.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace RevertMigration.Migrations +{ + /// + public partial class Hangar_HasDoors : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "HasDoors", + table: "Hangars", + type: "INTEGER", + nullable: false, + defaultValue: false); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "HasDoors", + table: "Hangars"); + } + } +} diff --git a/dotnet-efcore/RevertMigration/Migrations/AirportDbContextModelSnapshot.cs b/dotnet-efcore/RevertMigration/Migrations/AirportDbContextModelSnapshot.cs index aa09408b88..9bb4009fe3 100644 --- a/dotnet-efcore/RevertMigration/Migrations/AirportDbContextModelSnapshot.cs +++ b/dotnet-efcore/RevertMigration/Migrations/AirportDbContextModelSnapshot.cs @@ -13,7 +13,7 @@ partial class AirportDbContextModelSnapshot : ModelSnapshot protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "8.0.0"); + modelBuilder.HasAnnotation("ProductVersion", "10.0.12"); modelBuilder.Entity("Airplane", b => { @@ -21,27 +21,26 @@ protected override void BuildModel(ModelBuilder modelBuilder) .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); - b.Property("HangarId") + b.Property("HangarId") .HasColumnType("INTEGER"); - b.Property("MaxAirSpeed") + b.Property("MaxAirSpeed") .HasColumnType("REAL"); - b.Property("NumberOfEngines") + b.Property("NumberOfEngines") .HasColumnType("INTEGER"); - b.Property("RunsOnJetFuel") + b.Property("RunsOnJetFuel") .HasColumnType("INTEGER"); b.Property("TailNumber") - .IsRequired() .HasColumnType("TEXT"); b.HasKey("Id"); b.HasIndex("HangarId"); - b.ToTable("Airplanes", (string)null); + b.ToTable("Airplanes"); }); modelBuilder.Entity("Hangar", b => @@ -51,21 +50,21 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("INTEGER"); b.Property("HangarNumber") - .IsRequired() .HasColumnType("TEXT"); + b.Property("HasDoors") + .HasColumnType("INTEGER"); + b.HasKey("Id"); - b.ToTable("Hangars", (string)null); + b.ToTable("Hangars"); }); modelBuilder.Entity("Airplane", b => { b.HasOne("Hangar", "Hangar") .WithMany("Airplanes") - .HasForeignKey("HangarId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); + .HasForeignKey("HangarId"); b.Navigation("Hangar"); }); diff --git a/dotnet-efcore/RevertMigration/RevertMigration.Tests/MigrationRevertTests.cs b/dotnet-efcore/RevertMigration/RevertMigration.Tests/MigrationRevertTests.cs new file mode 100644 index 0000000000..30b454f52b --- /dev/null +++ b/dotnet-efcore/RevertMigration/RevertMigration.Tests/MigrationRevertTests.cs @@ -0,0 +1,106 @@ +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; + +public class MigrationRevertTests : IDisposable +{ + private const string InitMigration = "20231209224404_Init"; + private const string HasDoorsMigration = "20231209231131_Hangar_HasDoors"; + + private readonly string _dbPath = Path.Combine(Path.GetTempPath(), $"airport-{Guid.NewGuid():N}.db"); + + private AirportDbContext CreateContext() + { + // The sample keeps its database in LocalApplicationData; the tests use a throwaway file instead. + var db = new AirportDbContext(); + db.Database.SetConnectionString($"Data Source={_dbPath}"); + + return db; + } + + private static List GetHangarColumns(AirportDbContext db) + { + var connection = db.Database.GetDbConnection(); + connection.Open(); + try + { + using var command = connection.CreateCommand(); + command.CommandText = "SELECT name FROM pragma_table_info('Hangars');"; + using var reader = command.ExecuteReader(); + + var columns = new List(); + while (reader.Read()) + { + columns.Add(reader.GetString(0)); + } + + return columns; + } + finally + { + connection.Close(); + } + } + + [Fact] + public void WhenAllMigrationsAreApplied_ThenHangarsHasTheHasDoorsColumn() + { + using var db = CreateContext(); + + db.Database.Migrate(); + + Assert.Equal([InitMigration, HasDoorsMigration], db.Database.GetAppliedMigrations()); + Assert.Contains("HasDoors", GetHangarColumns(db)); + } + + [Fact] + public void WhenAllMigrationsAreApplied_ThenTheSampleCanSaveAHangarWithDoors() + { + using (var db = CreateContext()) + { + db.Database.Migrate(); + db.Add(new Hangar { HangarNumber = "HANGAR_01", HasDoors = true }); + db.SaveChanges(); + } + + using (var db = CreateContext()) + { + var hangar = db.Hangars.Single(); + + Assert.Equal("HANGAR_01", hangar.HangarNumber); + Assert.True(hangar.HasDoors); + } + } + + [Fact] + public void WhenDatabaseIsUpdatedToInit_ThenTheHasDoorsColumnIsGone() + { + using var db = CreateContext(); + db.Database.Migrate(); + + db.GetService().Migrate(InitMigration); + + Assert.Equal([InitMigration], db.Database.GetAppliedMigrations()); + Assert.Equal([HasDoorsMigration], db.Database.GetPendingMigrations()); + Assert.DoesNotContain("HasDoors", GetHangarColumns(db)); + } + + [Fact] + public void WhenDatabaseIsUpdatedToZero_ThenEveryMigrationIsReverted() + { + using var db = CreateContext(); + db.Database.Migrate(); + + db.GetService().Migrate(Migration.InitialDatabase); + + Assert.Empty(db.Database.GetAppliedMigrations()); + Assert.Empty(GetHangarColumns(db)); + } + + public void Dispose() + { + SqliteConnection.ClearAllPools(); + File.Delete(_dbPath); + } +} diff --git a/dotnet-efcore/RevertMigration/RevertMigration.Tests/RevertMigration.Tests.csproj b/dotnet-efcore/RevertMigration/RevertMigration.Tests/RevertMigration.Tests.csproj new file mode 100644 index 0000000000..d406e3d4eb --- /dev/null +++ b/dotnet-efcore/RevertMigration/RevertMigration.Tests/RevertMigration.Tests.csproj @@ -0,0 +1,32 @@ + + + + net10.0 + enable + enable + false + true + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + + diff --git a/dotnet-efcore/RevertMigration/RevertMigration.csproj b/dotnet-efcore/RevertMigration/RevertMigration.csproj index 9392e9abdb..3b8808ec91 100644 --- a/dotnet-efcore/RevertMigration/RevertMigration.csproj +++ b/dotnet-efcore/RevertMigration/RevertMigration.csproj @@ -2,17 +2,18 @@ Exe - net8.0 + net10.0 enable enable + $(DefaultItemExcludes);RevertMigration.Tests/** - + runtime; build; native; contentfiles; analyzers; buildtransitive all - + diff --git a/dotnet-efcore/RevertMigration/RevertMigration.sln b/dotnet-efcore/RevertMigration/RevertMigration.sln index a2a07939b2..9d7ac64b06 100644 --- a/dotnet-efcore/RevertMigration/RevertMigration.sln +++ b/dotnet-efcore/RevertMigration/RevertMigration.sln @@ -1,20 +1,46 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 -VisualStudioVersion = 17.5.002.0 +VisualStudioVersion = 17.5.2.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RevertMigration", "RevertMigration.csproj", "{C4FB3D79-BCE1-4C08-A791-A32F5B4E24CD}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RevertMigration.Tests", "RevertMigration.Tests\RevertMigration.Tests.csproj", "{9AD6860E-FC02-4404-87CC-8B4F71919AA8}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {C4FB3D79-BCE1-4C08-A791-A32F5B4E24CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C4FB3D79-BCE1-4C08-A791-A32F5B4E24CD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C4FB3D79-BCE1-4C08-A791-A32F5B4E24CD}.Debug|x64.ActiveCfg = Debug|Any CPU + {C4FB3D79-BCE1-4C08-A791-A32F5B4E24CD}.Debug|x64.Build.0 = Debug|Any CPU + {C4FB3D79-BCE1-4C08-A791-A32F5B4E24CD}.Debug|x86.ActiveCfg = Debug|Any CPU + {C4FB3D79-BCE1-4C08-A791-A32F5B4E24CD}.Debug|x86.Build.0 = Debug|Any CPU {C4FB3D79-BCE1-4C08-A791-A32F5B4E24CD}.Release|Any CPU.ActiveCfg = Release|Any CPU {C4FB3D79-BCE1-4C08-A791-A32F5B4E24CD}.Release|Any CPU.Build.0 = Release|Any CPU + {C4FB3D79-BCE1-4C08-A791-A32F5B4E24CD}.Release|x64.ActiveCfg = Release|Any CPU + {C4FB3D79-BCE1-4C08-A791-A32F5B4E24CD}.Release|x64.Build.0 = Release|Any CPU + {C4FB3D79-BCE1-4C08-A791-A32F5B4E24CD}.Release|x86.ActiveCfg = Release|Any CPU + {C4FB3D79-BCE1-4C08-A791-A32F5B4E24CD}.Release|x86.Build.0 = Release|Any CPU + {9AD6860E-FC02-4404-87CC-8B4F71919AA8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9AD6860E-FC02-4404-87CC-8B4F71919AA8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9AD6860E-FC02-4404-87CC-8B4F71919AA8}.Debug|x64.ActiveCfg = Debug|Any CPU + {9AD6860E-FC02-4404-87CC-8B4F71919AA8}.Debug|x64.Build.0 = Debug|Any CPU + {9AD6860E-FC02-4404-87CC-8B4F71919AA8}.Debug|x86.ActiveCfg = Debug|Any CPU + {9AD6860E-FC02-4404-87CC-8B4F71919AA8}.Debug|x86.Build.0 = Debug|Any CPU + {9AD6860E-FC02-4404-87CC-8B4F71919AA8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9AD6860E-FC02-4404-87CC-8B4F71919AA8}.Release|Any CPU.Build.0 = Release|Any CPU + {9AD6860E-FC02-4404-87CC-8B4F71919AA8}.Release|x64.ActiveCfg = Release|Any CPU + {9AD6860E-FC02-4404-87CC-8B4F71919AA8}.Release|x64.Build.0 = Release|Any CPU + {9AD6860E-FC02-4404-87CC-8B4F71919AA8}.Release|x86.ActiveCfg = Release|Any CPU + {9AD6860E-FC02-4404-87CC-8B4F71919AA8}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE