Skip to content

Commit

Permalink
Fix to #27375 - EF Core 6.0 temporal tables - migration rollback does…
Browse files Browse the repository at this point in the history
…n't work (#27379)

Problem was that we were incorrectly using old table schema rather than history table schema when we were creating DropTableOperation for the history table, after versioning has been disabled.
Fix is to use the correct value.

Fixes #27375
  • Loading branch information
maumar authored Mar 2, 2022
1 parent 6c76bd9 commit cc54787
Show file tree
Hide file tree
Showing 2 changed files with 246 additions and 6 deletions.
25 changes: 19 additions & 6 deletions src/EFCore.SqlServer/Migrations/SqlServerMigrationsSqlGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2573,12 +2573,25 @@ alterTableOperation.OldTable[SqlServerAnnotationNames.TemporalHistoryTableSchema

if (historyTableName != null)
{
operations.Add(
new DropTableOperation
{
Name = historyTableName,
Schema = alterTableOperation.OldTable.Schema
});
var useOldBehavior27375 = AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue27375", out var enabled27375) && enabled27375;
if (!useOldBehavior27375)
{
operations.Add(
new DropTableOperation
{
Name = historyTableName,
Schema = historyTableSchema
});
}
else
{
operations.Add(
new DropTableOperation
{
Name = historyTableName,
Schema = alterTableOperation.OldTable.Schema
});
}
}

operations.Add(operation);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3015,6 +3015,233 @@ FROM [sys].[default_constraints] [d]
@"DROP TABLE [HistoryTable];");
}

[ConditionalFact]
public virtual async Task Convert_temporal_table_with_explicit_history_table_schema_to_normal_table()
{
await Test(
builder => builder.Entity(
"Customer", e =>
{
e.Property<int>("Id").ValueGeneratedOnAdd();
e.Property<string>("Name");
e.Property<DateTime>("PeriodStart").ValueGeneratedOnAddOrUpdate();
e.Property<DateTime>("PeriodEnd").ValueGeneratedOnAddOrUpdate();
e.HasKey("Id");
e.ToTable(tb => tb.IsTemporal(ttb =>
{
ttb.UseHistoryTable("HistoryTable", "historySchema");
ttb.HasPeriodStart("PeriodStart");
ttb.HasPeriodEnd("PeriodEnd");
}));
}),
builder => builder.Entity(
"Customer", e =>
{
e.Property<int>("Id").ValueGeneratedOnAdd();
e.Property<string>("Name");
e.Property<DateTime>("PeriodStart");
e.Property<DateTime>("PeriodEnd");
e.HasKey("Id");
}),
model =>
{
var table = Assert.Single(model.Tables);
Assert.Equal("Customer", table.Name);
Assert.Null(table[SqlServerAnnotationNames.IsTemporal]);
Assert.Null(table[SqlServerAnnotationNames.TemporalHistoryTableName]);
Assert.Collection(
table.Columns,
c => Assert.Equal("Id", c.Name),
c => Assert.Equal("Name", c.Name),
c => Assert.Equal("PeriodEnd", c.Name),
c => Assert.Equal("PeriodStart", c.Name));
Assert.Same(
table.Columns.Single(c => c.Name == "Id"),
Assert.Single(table.PrimaryKey!.Columns));
});

AssertSql(
@"ALTER TABLE [Customer] SET (SYSTEM_VERSIONING = OFF)",
//
@"ALTER TABLE [Customer] DROP PERIOD FOR SYSTEM_TIME",
//
@"DROP TABLE [historySchema].[HistoryTable];");
}

[ConditionalFact]
public virtual async Task Convert_temporal_table_with_explicit_schemas_same_schema_for_table_and_history_to_normal_table()
{
await Test(
builder => builder.Entity(
"Customer", e =>
{
e.Property<int>("Id").ValueGeneratedOnAdd();
e.Property<string>("Name");
e.Property<DateTime>("PeriodStart").ValueGeneratedOnAddOrUpdate();
e.Property<DateTime>("PeriodEnd").ValueGeneratedOnAddOrUpdate();
e.HasKey("Id");
e.ToTable("Customer", "mySchema", tb => tb.IsTemporal(ttb =>
{
ttb.UseHistoryTable("HistoryTable", "mySchema");
ttb.HasPeriodStart("PeriodStart");
ttb.HasPeriodEnd("PeriodEnd");
}));
}),
builder => builder.Entity(
"Customer", e =>
{
e.ToTable("Customer", "mySchema");
e.Property<int>("Id").ValueGeneratedOnAdd();
e.Property<string>("Name");
e.Property<DateTime>("PeriodStart");
e.Property<DateTime>("PeriodEnd");
e.HasKey("Id");
}),
model =>
{
var table = Assert.Single(model.Tables);
Assert.Equal("Customer", table.Name);
Assert.Null(table[SqlServerAnnotationNames.IsTemporal]);
Assert.Null(table[SqlServerAnnotationNames.TemporalHistoryTableName]);
Assert.Collection(
table.Columns,
c => Assert.Equal("Id", c.Name),
c => Assert.Equal("Name", c.Name),
c => Assert.Equal("PeriodEnd", c.Name),
c => Assert.Equal("PeriodStart", c.Name));
Assert.Same(
table.Columns.Single(c => c.Name == "Id"),
Assert.Single(table.PrimaryKey!.Columns));
});

AssertSql(
@"ALTER TABLE [mySchema].[Customer] SET (SYSTEM_VERSIONING = OFF)",
//
@"ALTER TABLE [mySchema].[Customer] DROP PERIOD FOR SYSTEM_TIME",
//
@"DROP TABLE [mySchema].[HistoryTable];");
}

[ConditionalFact]
public virtual async Task Convert_temporal_table_using_custom_default_schema_to_normal_table()
{
await Test(

builder => builder.HasDefaultSchema("myDefaultSchema"),
builder => builder.Entity(
"Customer", e =>
{
e.Property<int>("Id").ValueGeneratedOnAdd();
e.Property<string>("Name");
e.Property<DateTime>("PeriodStart").ValueGeneratedOnAddOrUpdate();
e.Property<DateTime>("PeriodEnd").ValueGeneratedOnAddOrUpdate();
e.HasKey("Id");
e.ToTable("Customer", tb => tb.IsTemporal(ttb =>
{
ttb.UseHistoryTable("HistoryTable");
ttb.HasPeriodStart("PeriodStart");
ttb.HasPeriodEnd("PeriodEnd");
}));
}),
builder => builder.Entity(
"Customer", e =>
{
e.ToTable("Customer");
e.Property<int>("Id").ValueGeneratedOnAdd();
e.Property<string>("Name");
e.Property<DateTime>("PeriodStart");
e.Property<DateTime>("PeriodEnd");
e.HasKey("Id");
}),
model =>
{
var table = Assert.Single(model.Tables);
Assert.Equal("Customer", table.Name);
Assert.Null(table[SqlServerAnnotationNames.IsTemporal]);
Assert.Null(table[SqlServerAnnotationNames.TemporalHistoryTableName]);
Assert.Collection(
table.Columns,
c => Assert.Equal("Id", c.Name),
c => Assert.Equal("Name", c.Name),
c => Assert.Equal("PeriodEnd", c.Name),
c => Assert.Equal("PeriodStart", c.Name));
Assert.Same(
table.Columns.Single(c => c.Name == "Id"),
Assert.Single(table.PrimaryKey!.Columns));
});

AssertSql(
@"ALTER TABLE [myDefaultSchema].[Customer] SET (SYSTEM_VERSIONING = OFF)",
//
@"ALTER TABLE [myDefaultSchema].[Customer] DROP PERIOD FOR SYSTEM_TIME",
//
@"DROP TABLE [myDefaultSchema].[HistoryTable];");
}

[ConditionalFact]
public virtual async Task Convert_temporal_table_using_custom_default_schema_and_explicit_history_schema_to_normal_table()
{
await Test(

builder => builder.HasDefaultSchema("myDefaultSchema"),
builder => builder.Entity(
"Customer", e =>
{
e.Property<int>("Id").ValueGeneratedOnAdd();
e.Property<string>("Name");
e.Property<DateTime>("PeriodStart").ValueGeneratedOnAddOrUpdate();
e.Property<DateTime>("PeriodEnd").ValueGeneratedOnAddOrUpdate();
e.HasKey("Id");
e.ToTable("Customer", tb => tb.IsTemporal(ttb =>
{
ttb.UseHistoryTable("HistoryTable", "mySchema");
ttb.HasPeriodStart("PeriodStart");
ttb.HasPeriodEnd("PeriodEnd");
}));
}),
builder => builder.Entity(
"Customer", e =>
{
e.ToTable("Customer");
e.Property<int>("Id").ValueGeneratedOnAdd();
e.Property<string>("Name");
e.Property<DateTime>("PeriodStart");
e.Property<DateTime>("PeriodEnd");
e.HasKey("Id");
}),
model =>
{
var table = Assert.Single(model.Tables);
Assert.Equal("Customer", table.Name);
Assert.Null(table[SqlServerAnnotationNames.IsTemporal]);
Assert.Null(table[SqlServerAnnotationNames.TemporalHistoryTableName]);
Assert.Collection(
table.Columns,
c => Assert.Equal("Id", c.Name),
c => Assert.Equal("Name", c.Name),
c => Assert.Equal("PeriodEnd", c.Name),
c => Assert.Equal("PeriodStart", c.Name));
Assert.Same(
table.Columns.Single(c => c.Name == "Id"),
Assert.Single(table.PrimaryKey!.Columns));
});

AssertSql(
@"ALTER TABLE [myDefaultSchema].[Customer] SET (SYSTEM_VERSIONING = OFF)",
//
@"ALTER TABLE [myDefaultSchema].[Customer] DROP PERIOD FOR SYSTEM_TIME",
//
@"DROP TABLE [mySchema].[HistoryTable];");
}

[ConditionalFact]
public virtual async Task Convert_normal_table_to_temporal_table_with_minimal_configuration()
{
Expand Down

0 comments on commit cc54787

Please sign in to comment.