Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit b96c768

Browse files
authored
Fix to #27844 - EF Core 6.0 temporal table migration when altering computed column not generating correct script (#32398)
Working with computed column on a temporal table requires special processing - we need to disable the versioning, add the computed column to regular table and a column with the same name/type/position to the history table, but as a regular column. After all is done we re-enable versioning. Historical information on the computed column will be copied to the counterpart column in history table without issue. One thing that we can't do is modifying computed column SQL when table is temporal. What happens in case of CCSQL modification, we drop the column and create a new one with new CCSQL. This is not an issue for regular table, because the value is computed anyway, so old value is useless. But when we drop-create column, that column gets created at the last position in the table, and so the table no longer matches it's history table exactly (positions of columns may differ). We would have to drop+recreate column on a history table, but that results in losing historical data of that column. And that is valuable data, unlike for regular table. We could enable that scenario once/if we support table rebuilds. Fixes #27844
1 parent 74cc719 commit b96c768

File tree

4 files changed

+589
-50
lines changed

4 files changed

+589
-50
lines changed

src/EFCore.SqlServer/Migrations/SqlServerMigrationsSqlGenerator.cs

Lines changed: 70 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -2553,9 +2553,7 @@ private IReadOnlyList<MigrationOperation> RewriteOperations(
25532553
}
25542554

25552555
var isTemporalTable = renameTableOperation[SqlServerAnnotationNames.IsTemporal] as bool? == true;
2556-
if (isTemporalTable &&
2557-
!temporalInformation.DisabledVersioning &&
2558-
!temporalInformation.ShouldEnableVersioning)
2556+
if (isTemporalTable)
25592557
{
25602558
DisableVersioning(
25612559
tableName,
@@ -2636,16 +2634,12 @@ alterTableOperation.OldTable[SqlServerAnnotationNames.TemporalHistoryTableSchema
26362634
var oldPeriodEndColumnName =
26372635
alterTableOperation.OldTable[SqlServerAnnotationNames.TemporalPeriodEndColumnName] as string;
26382636

2639-
if (!temporalInformation.DisabledVersioning
2640-
&& !temporalInformation.ShouldEnableVersioning)
2641-
{
2642-
DisableVersioning(
2643-
tableName,
2644-
schema,
2645-
temporalInformation,
2646-
suppressTransaction,
2647-
shouldEnableVersioning: null);
2648-
}
2637+
DisableVersioning(
2638+
tableName,
2639+
schema,
2640+
temporalInformation,
2641+
suppressTransaction,
2642+
shouldEnableVersioning: null);
26492643

26502644
if (!temporalInformation.DisabledPeriod)
26512645
{
@@ -2692,27 +2686,39 @@ alterTableOperation.OldTable[SqlServerAnnotationNames.TemporalHistoryTableSchema
26922686
addColumnOperation.DefaultValue = DateTime.MaxValue;
26932687
}
26942688

2689+
var isSparse = addColumnOperation[SqlServerAnnotationNames.Sparse] as bool? == true;
2690+
var isComputed = addColumnOperation.ComputedColumnSql != null;
2691+
2692+
if (isSparse || isComputed)
2693+
{
2694+
DisableVersioning(
2695+
tableName,
2696+
schema,
2697+
temporalInformation,
2698+
suppressTransaction,
2699+
shouldEnableVersioning: true);
2700+
}
2701+
26952702
// when adding sparse column to temporal table, we need to disable versioning.
26962703
// This is because it may be the case that HistoryTable is using compression (by default)
26972704
// and the add column operation fails in that situation
26982705
// in order to make it work we need to disable versioning (if we haven't done it already)
26992706
// and de-compress the HistoryTable
2700-
if (addColumnOperation[SqlServerAnnotationNames.Sparse] as bool? == true)
2707+
if (isSparse)
27012708
{
2702-
if (!temporalInformation.DisabledVersioning
2703-
&& !temporalInformation.ShouldEnableVersioning)
2704-
{
2705-
DisableVersioning(
2706-
tableName,
2707-
schema,
2708-
temporalInformation,
2709-
suppressTransaction,
2710-
shouldEnableVersioning: true);
2711-
}
2712-
27132709
DecompressTable(temporalInformation.HistoryTableName!, temporalInformation.HistoryTableSchema, suppressTransaction);
27142710
}
27152711

2712+
if (addColumnOperation.ComputedColumnSql != null)
2713+
{
2714+
DisableVersioning(
2715+
tableName,
2716+
schema,
2717+
temporalInformation,
2718+
suppressTransaction,
2719+
shouldEnableVersioning: true);
2720+
}
2721+
27162722
operations.Add(addColumnOperation);
27172723

27182724
// when adding (non-period) column to an existing temporal table we need to check if we have disabled versioning
@@ -2725,6 +2731,16 @@ alterTableOperation.OldTable[SqlServerAnnotationNames.TemporalHistoryTableSchema
27252731
var addHistoryTableColumnOperation = CopyColumnOperation<AddColumnOperation>(addColumnOperation);
27262732
addHistoryTableColumnOperation.Table = temporalInformation.HistoryTableName!;
27272733
addHistoryTableColumnOperation.Schema = temporalInformation.HistoryTableSchema;
2734+
2735+
if (addHistoryTableColumnOperation.ComputedColumnSql != null)
2736+
{
2737+
// computed columns are not allowed inside HistoryTables
2738+
// but the historical computed value will be copied over to the non-computed counterpart,
2739+
// as long as their names and types (including nullability) match
2740+
// so we remove ComputedColumnSql info, so that the column in history table "appears normal"
2741+
addHistoryTableColumnOperation.ComputedColumnSql = null;
2742+
}
2743+
27282744
operations.Add(addHistoryTableColumnOperation);
27292745
}
27302746
}
@@ -2743,18 +2759,14 @@ alterTableOperation.OldTable[SqlServerAnnotationNames.TemporalHistoryTableSchema
27432759
var droppingPeriodColumn = dropColumnOperation.Name == temporalInformation.PeriodStartColumnName
27442760
|| dropColumnOperation.Name == temporalInformation.PeriodEndColumnName;
27452761

2746-
if (!temporalInformation.DisabledVersioning
2747-
&& !temporalInformation.ShouldEnableVersioning)
2748-
{
2749-
// if we are dropping non-period column, we should enable versioning at the end.
2750-
// When dropping period column there is no need - we are removing the versioning for this table altogether
2751-
DisableVersioning(
2752-
tableName,
2753-
schema,
2754-
temporalInformation,
2755-
suppressTransaction,
2756-
shouldEnableVersioning: droppingPeriodColumn ? null : true);
2757-
}
2762+
// if we are dropping non-period column, we should enable versioning at the end.
2763+
// When dropping period column there is no need - we are removing the versioning for this table altogether
2764+
DisableVersioning(
2765+
tableName,
2766+
schema,
2767+
temporalInformation,
2768+
suppressTransaction,
2769+
shouldEnableVersioning: droppingPeriodColumn ? null : true);
27582770

27592771
if (droppingPeriodColumn && !temporalInformation.DisabledPeriod)
27602772
{
@@ -2820,6 +2832,14 @@ alterTableOperation.OldTable[SqlServerAnnotationNames.TemporalHistoryTableSchema
28202832

28212833
if (temporalInformation.IsTemporalTable)
28222834
{
2835+
if (alterColumnOperation.OldColumn.ComputedColumnSql != alterColumnOperation.ComputedColumnSql)
2836+
{
2837+
throw new NotSupportedException(
2838+
SqlServerStrings.TemporalMigrationModifyingComputedColumnNotSupported(
2839+
alterColumnOperation.Name,
2840+
alterColumnOperation.Table));
2841+
}
2842+
28232843
// for alter column operation converting column from nullable to non-nullable in the temporal table
28242844
// we must disable versioning in order to properly handle it
28252845
// specifically, switching values in history table from null to the default value
@@ -2831,9 +2851,7 @@ alterTableOperation.OldTable[SqlServerAnnotationNames.TemporalHistoryTableSchema
28312851
var changeToSparse = alterColumnOperation.OldColumn[SqlServerAnnotationNames.Sparse] as bool? != true
28322852
&& alterColumnOperation[SqlServerAnnotationNames.Sparse] as bool? == true;
28332853

2834-
if ((changeToNonNullable || changeToSparse)
2835-
&& !temporalInformation.DisabledVersioning
2836-
&& !temporalInformation.ShouldEnableVersioning)
2854+
if (changeToNonNullable || changeToSparse)
28372855
{
28382856
DisableVersioning(
28392857
tableName!,
@@ -2878,9 +2896,7 @@ alterTableOperation.OldTable[SqlServerAnnotationNames.TemporalHistoryTableSchema
28782896

28792897
case DropPrimaryKeyOperation:
28802898
case AddPrimaryKeyOperation:
2881-
if (temporalInformation.IsTemporalTable
2882-
&& !temporalInformation.DisabledVersioning
2883-
&& !temporalInformation.ShouldEnableVersioning)
2899+
if (temporalInformation.IsTemporalTable)
28842900
{
28852901
DisableVersioning(
28862902
tableName!,
@@ -2948,16 +2964,20 @@ void DisableVersioning(
29482964
bool suppressTransaction,
29492965
bool? shouldEnableVersioning)
29502966
{
2951-
temporalInformation.DisabledVersioning = true;
2967+
if (!temporalInformation.DisabledVersioning
2968+
&& !temporalInformation.ShouldEnableVersioning)
2969+
{
2970+
temporalInformation.DisabledVersioning = true;
29522971

2953-
AddDisableVersioningOperation(tableName, schema, suppressTransaction);
2972+
AddDisableVersioningOperation(tableName, schema, suppressTransaction);
29542973

2955-
if (shouldEnableVersioning != null)
2956-
{
2957-
temporalInformation.ShouldEnableVersioning = shouldEnableVersioning.Value;
2958-
if (shouldEnableVersioning.Value)
2974+
if (shouldEnableVersioning != null)
29592975
{
2960-
temporalInformation.SuppressTransaction = suppressTransaction;
2976+
temporalInformation.ShouldEnableVersioning = shouldEnableVersioning.Value;
2977+
if (shouldEnableVersioning.Value)
2978+
{
2979+
temporalInformation.SuppressTransaction = suppressTransaction;
2980+
}
29612981
}
29622982
}
29632983
}

src/EFCore.SqlServer/Properties/SqlServerStrings.Designer.cs

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/EFCore.SqlServer/Properties/SqlServerStrings.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,9 @@
320320
<data name="TemporalExpectedPeriodPropertyNotFound" xml:space="preserve">
321321
<value>Entity type '{entityType}' mapped to temporal table does not contain the expected period property: '{propertyName}'.</value>
322322
</data>
323+
<data name="TemporalMigrationModifyingComputedColumnNotSupported" xml:space="preserve">
324+
<value>Modifying SQL of a computed column '{columnName}' on a temporal table '{tableName}' is not supported by migrations.</value>
325+
</data>
323326
<data name="TemporalMustDefinePeriodProperties" xml:space="preserve">
324327
<value>Entity type '{entityType}' mapped to temporal table must have a period start and a period end property.</value>
325328
</data>

0 commit comments

Comments
 (0)