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 != null || alterColumnOperation.OldColumn.DefaultValueSql != null)
&& alterColumnOperation.DefaultValue == null && alterColumnOperation.DefaultValueSql == null;
Comment thread
AndriySvyryd marked this conversation as resolved.
Outdated

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

Comment thread
AndriySvyryd marked this conversation as resolved.
[ConditionalFact]
public virtual async Task Temporal_table_with_default_constraint_can_alter_column()
{
await Test(
builder => { },
builder => builder.Entity(
"Customer", e =>
{
e.Property<int>("Id").ValueGeneratedOnAdd();
e.Property<string>("Name").HasMaxLength(50).HasDefaultValue("DefaultName");
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<int>("Id").ValueGeneratedOnAdd();
e.Property<string>("Name").HasMaxLength(100); // Remove default value
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");
}));
}),
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 @historyTableSchema nvarchar(max) = QUOTENAME(SCHEMA_NAME())
EXEC(N'CREATE TABLE [Customer] (
[Id] int NOT NULL IDENTITY,
[Name] nvarchar(50) NOT NULL DEFAULT N''DefaultName'',
[SystemTimeEnd] datetime2 GENERATED ALWAYS AS ROW END HIDDEN NOT NULL,
[SystemTimeStart] datetime2 GENERATED ALWAYS AS ROW START HIDDEN NOT NULL,
CONSTRAINT [PK_Customer] PRIMARY KEY ([Id]),
PERIOD FOR SYSTEM_TIME([SystemTimeStart], [SystemTimeEnd])
) WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = ' + @historyTableSchema + N'.[CustomerHistory]))');
""",
//
"""
ALTER TABLE [Customer] SET (SYSTEM_VERSIONING = OFF)
""",
//
"""
DECLARE @var sysname;
SELECT @var = [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 @var IS NOT NULL EXEC(N'ALTER TABLE [Customer] DROP CONSTRAINT [' + @var + '];');
""",
//
"""
ALTER TABLE [Customer] ALTER COLUMN [Name] nvarchar(100) NOT NULL;
""",
//
"""
ALTER TABLE [CustomerHistory] ALTER COLUMN [Name] nvarchar(100) NOT NULL;
""",
//
"""
DECLARE @historyTableSchema1 nvarchar(max) = QUOTENAME(SCHEMA_NAME())
EXEC(N'ALTER TABLE [Customer] SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = ' + @historyTableSchema1 + N'.[CustomerHistory]))')
""");
}

[ConditionalFact]
public virtual async Task Temporal_table_add_default_constraint_to_column()
{
await Test(
builder => { },
builder => builder.Entity(
"Customer", e =>
{
e.Property<int>("Id").ValueGeneratedOnAdd();
e.Property<string>("Name").HasMaxLength(50);
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<int>("Id").ValueGeneratedOnAdd();
e.Property<string>("Name").HasMaxLength(50).HasDefaultValue("DefaultName"); // Add default value
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");
}));
}),
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 @historyTableSchema nvarchar(max) = QUOTENAME(SCHEMA_NAME())
EXEC(N'CREATE TABLE [Customer] (
[Id] int NOT NULL IDENTITY,
[Name] nvarchar(50) NULL,
[SystemTimeEnd] datetime2 GENERATED ALWAYS AS ROW END HIDDEN NOT NULL,
[SystemTimeStart] datetime2 GENERATED ALWAYS AS ROW START HIDDEN NOT NULL,
CONSTRAINT [PK_Customer] PRIMARY KEY ([Id]),
PERIOD FOR SYSTEM_TIME([SystemTimeStart], [SystemTimeEnd])
) WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = ' + @historyTableSchema + N'.[CustomerHistory]))');
""",
//
"""
ALTER TABLE [Customer] SET (SYSTEM_VERSIONING = OFF)
""",
//
"""
ALTER TABLE [Customer] ALTER COLUMN [Name] nvarchar(50) NOT NULL;
""",
//
"""
ALTER TABLE [CustomerHistory] ALTER COLUMN [Name] nvarchar(50) NOT NULL;
""",
//
"""
ALTER TABLE [Customer] ADD DEFAULT N'DefaultName' FOR [Name];
""",
//
"""
DECLARE @historyTableSchema1 nvarchar(max) = QUOTENAME(SCHEMA_NAME())
EXEC(N'ALTER TABLE [Customer] SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = ' + @historyTableSchema1 + N'.[CustomerHistory]))')
""");
}

[ConditionalFact]
public virtual async Task Temporal_table_change_default_constraint_value()
{
await Test(
builder => { },
builder => builder.Entity(
"Customer", e =>
{
e.Property<int>("Id").ValueGeneratedOnAdd();
e.Property<string>("Name").HasMaxLength(50).HasDefaultValue("OldDefault");
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<int>("Id").ValueGeneratedOnAdd();
e.Property<string>("Name").HasMaxLength(50).HasDefaultValue("NewDefault"); // Change default value
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");
}));
}),
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 @historyTableSchema nvarchar(max) = QUOTENAME(SCHEMA_NAME())
EXEC(N'CREATE TABLE [Customer] (
[Id] int NOT NULL IDENTITY,
[Name] nvarchar(50) NOT NULL DEFAULT N'OldDefault',
[SystemTimeEnd] datetime2 GENERATED ALWAYS AS ROW END HIDDEN NOT NULL,
[SystemTimeStart] datetime2 GENERATED ALWAYS AS ROW START HIDDEN NOT NULL,
CONSTRAINT [PK_Customer] PRIMARY KEY ([Id]),
PERIOD FOR SYSTEM_TIME([SystemTimeStart], [SystemTimeEnd])
) WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = ' + @historyTableSchema + N'.[CustomerHistory]))');
""",
//
"""
ALTER TABLE [Customer] SET (SYSTEM_VERSIONING = OFF)
""",
//
"""
DECLARE @var sysname;
SELECT @var = [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 @var IS NOT NULL EXEC(N'ALTER TABLE [Customer] DROP CONSTRAINT [' + @var + '];');
""",
//
"""
ALTER TABLE [Customer] ALTER COLUMN [Name] nvarchar(50) NOT NULL;
""",
//
"""
ALTER TABLE [CustomerHistory] ALTER COLUMN [Name] nvarchar(50) NOT NULL;
""",
//
"""
ALTER TABLE [Customer] ADD DEFAULT N'NewDefault' FOR [Name];
""",
//
"""
DECLARE @historyTableSchema1 nvarchar(max) = QUOTENAME(SCHEMA_NAME())
EXEC(N'ALTER TABLE [Customer] SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = ' + @historyTableSchema1 + N'.[CustomerHistory]))')
""");
}
}
Loading