dotnet / dotnet/efcore

Inconsistent versioned table management in migrations

Open
#36,029 0 comments 0 reactions 1 assignee Claimed by @AndriySvyryd View on GitHub
area-migrations area-temporal-tables customer-reported needs-design
Dominant language
C#
Stars
14.8k
Forks
3.4k
PR merge metrics
PR metrics pending

Description

### Bug description

Hi! I noticed when using a custom script in a migration that the management of the temporal versioning is inconsistent and unexpected, at least to me

If there's a change to a versioned-table's schema in the migration, it seems that the generated migration:
1. Turns off versioning the first time it encounters a schema change to a temporally managed table.
2. Wraps the rest of the migration script in that transaction/de-versioned time.
3. Re-enables the versioning as the last act of the migration script.

If there's no change to a versioned-table's schema in the migration, the generated migration doesn't turn off/on versioning at all.

The result is very inconsistent version management when using custom SQL and can easily result in unexpected data management. For example, if the custom script is **before** a versioned table's schema changes, the updates would be captured by versioning. If the custom script is **anywhere after** a change to a versioned table's schema, the updates **would not** be captured by versioning (i.e., nothing would be added to the associated XXXHistory table.

Below you can see the inconsistent (or at least very unexpected to me) versioning results with custom scripting:

Personally, I wouldn't expect the location of the custom script to impact data storage. And while I'm assuming the reason for leaving the versioning off until the end of the script is to avoid the back and forth of versioning off/on multiple times, I wouldn't expect any custom script to be "wrapped" in an unversioned transaction unless I specifically write it that way.

Happy to help with this however I can - thanks!

### Your code

```csharp
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql("UPDATE [Inspection] SET [Notes] = 'Versioned'");

migrationBuilder.DropColumn(
name: "County",
table: "InspectionZone");

migrationBuilder.Sql("UPDATE [Inspection] SET [Notes] = 'Again Versioned'");

migrationBuilder.DropColumn(
name: "Inspector",
table: "Inspection");

migrationBuilder.Sql("UPDATE [Inspection] SET [Notes] = 'Unversioned'");
}

..produces..

BEGIN TRANSACTION;
UPDATE [Inspection] SET [Notes] = 'Versioned'

DECLARE @var1 sysname;
SELECT @var1 = [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'[InspectionZone]') AND [c].[name] = N'County');
IF @var1 IS NOT NULL EXEC(N'ALTER TABLE [InspectionZone] DROP CONSTRAINT [' + @var1 + '];');
ALTER TABLE [InspectionZone] DROP COLUMN [County];

UPDATE [Inspection] SET [Notes] = 'Again Versioned'

ALTER TABLE [Inspection] SET (SYSTEM_VERSIONING = OFF)

DECLARE @var2 sysname;
SELECT @var2 = [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'[Inspection]') AND [c].[name] = N'Inspector');
IF @var2 IS NOT NULL EXEC(N'ALTER TABLE [Inspection] DROP CONSTRAINT [' + @var2 + '];');
ALTER TABLE [Inspection] DROP COLUMN [Inspector];

DECLARE @var3 sysname;
SELECT @var3 = [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'[InspectionHistory]') AND [c].[name] = N'Inspector');
IF @var3 IS NOT NULL EXEC(N'ALTER TABLE [InspectionHistory] DROP CONSTRAINT [' + @var3 + '];');
ALTER TABLE [InspectionHistory] DROP COLUMN [Inspector];

UPDATE [Inspection] SET [Notes] = 'Unversioned'

DECLARE @historyTableSchema sysname = SCHEMA_NAME()
EXEC(N'ALTER TABLE [Inspection] SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = [' + @historyTableSchema + '].[InspectionHistory]))')

INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion])
VALUES (N'20250428192026_CustomScriptVersioning', N'9.0.4');

COMMIT;
GO
```

### EF Core version

9.0.4

### Database provider

Microsoft.EntityFrameworkCore.SqlServer

### Target framework

NET9

### Operating system

Windows 10

### IDE

Visual Studio Professional 17.13.3

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.