Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -3118,7 +3118,12 @@ alterTableOperation.OldTable[SqlServerAnnotationNames.TemporalHistoryTableSchema
var changeToSparse = alterColumnOperation.OldColumn[SqlServerAnnotationNames.Sparse] as bool? != true
&& alterColumnOperation[SqlServerAnnotationNames.Sparse] as bool? == true;

if (changeToNonNullable || changeToSparse)
// for alter column removing default value we also need to disable versioning
// because the default constraint needs to be removed from both main and history tables
var removingDefaultValue = (alterColumnOperation.OldColumn.DefaultValue is not null || alterColumnOperation.OldColumn.DefaultValueSql is not null)
&& alterColumnOperation.DefaultValue is null && alterColumnOperation.DefaultValueSql is null;

if (changeToNonNullable || changeToSparse || removingDefaultValue)
{
DisableVersioning(
tableName!,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10174,6 +10174,274 @@ CREATE TABLE [Customers] (
[Name] nvarchar(max) NULL,
CONSTRAINT [PK_Customers] PRIMARY KEY ([Id])
);
""");
}

[ConditionalFact]
public virtual async Task Temporal_table_with_default_constraint_can_alter_column()
{
await Test(
builder => builder.Entity(
"Customer", e =>
{
e.Property<int>("Id").ValueGeneratedOnAdd();
e.Property<DateTime>("SystemTimeStart").ValueGeneratedOnAddOrUpdate();
e.Property<DateTime>("SystemTimeEnd").ValueGeneratedOnAddOrUpdate();
e.HasKey("Id");

e.ToTable(tb => tb.IsTemporal(ttb =>
{
ttb.HasPeriodStart("SystemTimeStart");
ttb.HasPeriodEnd("SystemTimeEnd");
}));
}),
builder => builder.Entity(
"Customer", e =>
{
e.Property<string>("Name").HasMaxLength(50).HasDefaultValue("DefaultName");
}),
builder => builder.Entity(
"Customer", e =>
{
e.Property<string>("Name").HasMaxLength(100); // Remove default value
}),
model =>
{
var table = Assert.Single(model.Tables);
Assert.Equal("Customer", table.Name);
Assert.Equal(true, table[SqlServerAnnotationNames.IsTemporal]);
Assert.Equal("CustomerHistory", table[SqlServerAnnotationNames.TemporalHistoryTableName]);
Assert.Equal("SystemTimeStart", table[SqlServerAnnotationNames.TemporalPeriodStartPropertyName]);
Assert.Equal("SystemTimeEnd", table[SqlServerAnnotationNames.TemporalPeriodEndPropertyName]);

Assert.Collection(
table.Columns,
c => Assert.Equal("Id", c.Name),
c => Assert.Equal("Name", c.Name));
Assert.Same(
table.Columns.Single(c => c.Name == "Id"),
Assert.Single(table.PrimaryKey!.Columns));
});

AssertSql(
"""
ALTER TABLE [Customer] SET (SYSTEM_VERSIONING = OFF)
""",
//
"""
DECLARE @var2 nvarchar(max);
SELECT @var2 = QUOTENAME([d].[name])
FROM [sys].[default_constraints] [d]
INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id]
WHERE ([d].[parent_object_id] = OBJECT_ID(N'[Customer]') AND [c].[name] = N'Name');
IF @var2 IS NOT NULL EXEC(N'ALTER TABLE [Customer] DROP CONSTRAINT ' + @var2 + ';');
ALTER TABLE [Customer] ALTER COLUMN [Name] nvarchar(100) NULL;
""",
//
"""
DECLARE @var3 nvarchar(max);
SELECT @var3 = QUOTENAME([d].[name])
FROM [sys].[default_constraints] [d]
INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id]
WHERE ([d].[parent_object_id] = OBJECT_ID(N'[CustomerHistory]') AND [c].[name] = N'Name');
IF @var3 IS NOT NULL EXEC(N'ALTER TABLE [CustomerHistory] DROP CONSTRAINT ' + @var3 + ';');
ALTER TABLE [CustomerHistory] ALTER COLUMN [Name] nvarchar(100) NULL;
""",
//
"""
DECLARE @historyTableSchema1 nvarchar(max) = QUOTENAME(SCHEMA_NAME())
EXEC(N'ALTER TABLE [Customer] SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = ' + @historyTableSchema1 + '.[CustomerHistory]))')
""");
}

[ConditionalFact]
public virtual async Task Temporal_table_add_default_constraint_to_column()
{
await Test(
builder => builder.Entity(
"Customer", e =>
{
e.Property<int>("Id").ValueGeneratedOnAdd();
e.Property<DateTime>("SystemTimeStart").ValueGeneratedOnAddOrUpdate();
e.Property<DateTime>("SystemTimeEnd").ValueGeneratedOnAddOrUpdate();
e.HasKey("Id");

e.ToTable(tb => tb.IsTemporal(ttb =>
{
ttb.HasPeriodStart("SystemTimeStart");
ttb.HasPeriodEnd("SystemTimeEnd");
}));
}),
builder => builder.Entity(
"Customer", e =>
{
e.Property<string>("Name").HasMaxLength(50);
}),
builder => builder.Entity(
"Customer", e =>
{
e.Property<string>("Name").HasMaxLength(50).HasDefaultValue("DefaultName"); // Add default value
}),
model =>
{
var table = Assert.Single(model.Tables);
Assert.Equal("Customer", table.Name);
Assert.Equal(true, table[SqlServerAnnotationNames.IsTemporal]);
Assert.Equal("CustomerHistory", table[SqlServerAnnotationNames.TemporalHistoryTableName]);
Assert.Equal("SystemTimeStart", table[SqlServerAnnotationNames.TemporalPeriodStartPropertyName]);
Assert.Equal("SystemTimeEnd", table[SqlServerAnnotationNames.TemporalPeriodEndPropertyName]);

Assert.Collection(
table.Columns,
c => Assert.Equal("Id", c.Name),
c => Assert.Equal("Name", c.Name));
Assert.Same(
table.Columns.Single(c => c.Name == "Id"),
Assert.Single(table.PrimaryKey!.Columns));
});

AssertSql(
"""
DECLARE @var1 nvarchar(max);
SELECT @var1 = QUOTENAME([d].[name])
FROM [sys].[default_constraints] [d]
INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id]
WHERE ([d].[parent_object_id] = OBJECT_ID(N'[Customer]') AND [c].[name] = N'Name');
IF @var1 IS NOT NULL EXEC(N'ALTER TABLE [Customer] DROP CONSTRAINT ' + @var1 + ';');
ALTER TABLE [Customer] ADD DEFAULT N'DefaultName' FOR [Name];
""");
}

[ConditionalFact]
public virtual async Task Temporal_table_change_default_constraint_value()
{
await Test(
builder => builder.Entity(
"Customer", e =>
{
e.Property<int>("Id").ValueGeneratedOnAdd();
e.Property<DateTime>("SystemTimeStart").ValueGeneratedOnAddOrUpdate();
e.Property<DateTime>("SystemTimeEnd").ValueGeneratedOnAddOrUpdate();
e.HasKey("Id");

e.ToTable(tb => tb.IsTemporal(ttb =>
{
ttb.HasPeriodStart("SystemTimeStart");
ttb.HasPeriodEnd("SystemTimeEnd");
}));
}),
builder => builder.Entity(
"Customer", e =>
{
e.Property<string>("Name").HasMaxLength(50).HasDefaultValue("OldDefault");
}),
builder => builder.Entity(
"Customer", e =>
{
e.Property<string>("Name").HasMaxLength(50).HasDefaultValue("NewDefault"); // Change default value
}),
model =>
{
var table = Assert.Single(model.Tables);
Assert.Equal("Customer", table.Name);
Assert.Equal(true, table[SqlServerAnnotationNames.IsTemporal]);
Assert.Equal("CustomerHistory", table[SqlServerAnnotationNames.TemporalHistoryTableName]);
Assert.Equal("SystemTimeStart", table[SqlServerAnnotationNames.TemporalPeriodStartPropertyName]);
Assert.Equal("SystemTimeEnd", table[SqlServerAnnotationNames.TemporalPeriodEndPropertyName]);

Assert.Collection(
table.Columns,
c => Assert.Equal("Id", c.Name),
c => Assert.Equal("Name", c.Name));
Assert.Same(
table.Columns.Single(c => c.Name == "Id"),
Assert.Single(table.PrimaryKey!.Columns));
});

AssertSql(
"""
DECLARE @var1 nvarchar(max);
SELECT @var1 = QUOTENAME([d].[name])
FROM [sys].[default_constraints] [d]
INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id]
WHERE ([d].[parent_object_id] = OBJECT_ID(N'[Customer]') AND [c].[name] = N'Name');
IF @var1 IS NOT NULL EXEC(N'ALTER TABLE [Customer] DROP CONSTRAINT ' + @var1 + ';');
ALTER TABLE [Customer] ADD DEFAULT N'NewDefault' FOR [Name];
""");
}

[ConditionalFact]
public virtual async Task Temporal_table_remove_default_value_sql_from_column()
{
await Test(
builder => builder.Entity(
"Customer", e =>
{
e.Property<int>("Id").ValueGeneratedOnAdd();
e.Property<DateTime>("SystemTimeStart").ValueGeneratedOnAddOrUpdate();
e.Property<DateTime>("SystemTimeEnd").ValueGeneratedOnAddOrUpdate();
e.HasKey("Id");

e.ToTable(tb => tb.IsTemporal(ttb =>
{
ttb.HasPeriodStart("SystemTimeStart");
ttb.HasPeriodEnd("SystemTimeEnd");
}));
}),
builder => builder.Entity(
"Customer", e =>
{
e.Property<DateTime>("CreatedDate").HasDefaultValueSql("GETDATE()");
}),
builder => builder.Entity(
"Customer", e =>
{
e.Property<DateTime>("CreatedDate"); // Remove default value SQL
}),
model =>
{
var table = Assert.Single(model.Tables);
Assert.Equal("Customer", table.Name);
Assert.Equal(true, table[SqlServerAnnotationNames.IsTemporal]);
Assert.Equal("CustomerHistory", table[SqlServerAnnotationNames.TemporalHistoryTableName]);
Assert.Equal("SystemTimeStart", table[SqlServerAnnotationNames.TemporalPeriodStartPropertyName]);
Assert.Equal("SystemTimeEnd", table[SqlServerAnnotationNames.TemporalPeriodEndPropertyName]);

Assert.Collection(
table.Columns,
c => Assert.Equal("Id", c.Name),
c => Assert.Equal("CreatedDate", c.Name));
Assert.Same(
table.Columns.Single(c => c.Name == "Id"),
Assert.Single(table.PrimaryKey!.Columns));
});

AssertSql(
"""
ALTER TABLE [Customer] SET (SYSTEM_VERSIONING = OFF)
""",
//
"""
DECLARE @var2 nvarchar(max);
SELECT @var2 = QUOTENAME([d].[name])
FROM [sys].[default_constraints] [d]
INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id]
WHERE ([d].[parent_object_id] = OBJECT_ID(N'[Customer]') AND [c].[name] = N'CreatedDate');
IF @var2 IS NOT NULL EXEC(N'ALTER TABLE [Customer] DROP CONSTRAINT ' + @var2 + ';');
""",
//
"""
DECLARE @var3 nvarchar(max);
SELECT @var3 = QUOTENAME([d].[name])
FROM [sys].[default_constraints] [d]
INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id]
WHERE ([d].[parent_object_id] = OBJECT_ID(N'[CustomerHistory]') AND [c].[name] = N'CreatedDate');
IF @var3 IS NOT NULL EXEC(N'ALTER TABLE [CustomerHistory] DROP CONSTRAINT ' + @var3 + ';');
""",
//
"""
DECLARE @historyTableSchema1 nvarchar(max) = QUOTENAME(SCHEMA_NAME())
EXEC(N'ALTER TABLE [Customer] SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = ' + @historyTableSchema1 + '.[CustomerHistory]))')
""");
}
}
Loading