feat(templates): phase 3 — migrate existing compositions to derived
EF migration MigrateCompositionsToDerived. Aborts with a clear error if
any '<parent>.<slot>' derived name would collide with an existing
template. Otherwise it cursor-walks every TemplateComposition that still
points at a non-derived template:
1. Insert a derived Template (name "<parent>.<slot>",
ParentTemplateId=base, IsDerived=1, OwnerCompositionId=composition).
2. Copy base attributes / scripts into the derived row with
IsInherited=1, LockedInDerived=0.
3. Repoint TemplateComposition.ComposedTemplateId at the new derived.
Idempotent: only touches compositions whose target is IsDerived=0, so
re-runs and freshly-created Phase 2 compositions are skipped.
Down() reverses by repointing compositions back to derived.ParentTemplateId
and dropping all derived templates (with cascade copy rows).
This commit is contained in:
1300
src/ScadaLink.ConfigurationDatabase/Migrations/20260512122746_MigrateCompositionsToDerived.Designer.cs
generated
Normal file
1300
src/ScadaLink.ConfigurationDatabase/Migrations/20260512122746_MigrateCompositionsToDerived.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,117 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace ScadaLink.ConfigurationDatabase.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class MigrateCompositionsToDerived : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
// Re-shape every pre-Phase-2 TemplateComposition so it points at a
|
||||
// newly created derived template ("<parent>.<slot>") that inherits
|
||||
// from the original base. Attribute and script rows are copied with
|
||||
// IsInherited=1; the composition's ComposedTemplateId is repointed.
|
||||
//
|
||||
// Idempotent: only rows whose target is still IsDerived=0 are touched.
|
||||
// Aborts the migration if any derived name would collide with an
|
||||
// existing template, so the operator can resolve manually.
|
||||
migrationBuilder.Sql(@"
|
||||
SET NOCOUNT ON;
|
||||
|
||||
DECLARE @collisions NVARCHAR(MAX) = (
|
||||
SELECT STRING_AGG(owner.Name + N'.' + c.InstanceName, N', ')
|
||||
FROM TemplateCompositions c
|
||||
INNER JOIN Templates base_t ON base_t.Id = c.ComposedTemplateId
|
||||
INNER JOIN Templates owner ON owner.Id = c.TemplateId
|
||||
INNER JOIN Templates existing ON existing.Name = owner.Name + N'.' + c.InstanceName
|
||||
WHERE base_t.IsDerived = 0
|
||||
);
|
||||
IF @collisions IS NOT NULL
|
||||
BEGIN
|
||||
DECLARE @msg NVARCHAR(MAX) =
|
||||
N'MigrateCompositionsToDerived: cannot create derived templates — these names already exist: '
|
||||
+ @collisions
|
||||
+ N'. Rename the conflicting templates and retry the migration.';
|
||||
THROW 50000, @msg, 1;
|
||||
END
|
||||
|
||||
DECLARE @CompId INT, @BaseId INT, @OwnerName NVARCHAR(200), @SlotName NVARCHAR(200);
|
||||
DECLARE @NewId INT, @NewName NVARCHAR(200);
|
||||
|
||||
DECLARE map_cursor CURSOR FAST_FORWARD FOR
|
||||
SELECT c.Id, c.ComposedTemplateId, owner.Name, c.InstanceName
|
||||
FROM TemplateCompositions c
|
||||
INNER JOIN Templates base_t ON base_t.Id = c.ComposedTemplateId
|
||||
INNER JOIN Templates owner ON owner.Id = c.TemplateId
|
||||
WHERE base_t.IsDerived = 0;
|
||||
|
||||
OPEN map_cursor;
|
||||
FETCH NEXT FROM map_cursor INTO @CompId, @BaseId, @OwnerName, @SlotName;
|
||||
|
||||
WHILE @@FETCH_STATUS = 0
|
||||
BEGIN
|
||||
SET @NewName = @OwnerName + N'.' + @SlotName;
|
||||
|
||||
INSERT INTO Templates (Name, Description, ParentTemplateId, FolderId, IsDerived, OwnerCompositionId)
|
||||
SELECT @NewName, b.Description, b.Id, NULL, 1, @CompId
|
||||
FROM Templates b
|
||||
WHERE b.Id = @BaseId;
|
||||
|
||||
SET @NewId = SCOPE_IDENTITY();
|
||||
|
||||
INSERT INTO TemplateAttributes
|
||||
(TemplateId, Name, Value, DataType, IsLocked, Description, DataSourceReference, IsInherited, LockedInDerived)
|
||||
SELECT @NewId, a.Name, a.Value, a.DataType, a.IsLocked, a.Description, a.DataSourceReference, 1, 0
|
||||
FROM TemplateAttributes a
|
||||
WHERE a.TemplateId = @BaseId;
|
||||
|
||||
INSERT INTO TemplateScripts
|
||||
(TemplateId, Name, Code, IsLocked, TriggerType, TriggerConfiguration, ParameterDefinitions, ReturnDefinition, MinTimeBetweenRuns, IsInherited, LockedInDerived)
|
||||
SELECT @NewId, s.Name, s.Code, s.IsLocked, s.TriggerType, s.TriggerConfiguration, s.ParameterDefinitions, s.ReturnDefinition, s.MinTimeBetweenRuns, 1, 0
|
||||
FROM TemplateScripts s
|
||||
WHERE s.TemplateId = @BaseId;
|
||||
|
||||
UPDATE TemplateCompositions
|
||||
SET ComposedTemplateId = @NewId
|
||||
WHERE Id = @CompId;
|
||||
|
||||
FETCH NEXT FROM map_cursor INTO @CompId, @BaseId, @OwnerName, @SlotName;
|
||||
END
|
||||
|
||||
CLOSE map_cursor;
|
||||
DEALLOCATE map_cursor;
|
||||
");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
// Reverse: repoint each composition back to the derived template's
|
||||
// base, then drop the derived templates (with their copied rows).
|
||||
migrationBuilder.Sql(@"
|
||||
SET NOCOUNT ON;
|
||||
|
||||
UPDATE c
|
||||
SET c.ComposedTemplateId = d.ParentTemplateId
|
||||
FROM TemplateCompositions c
|
||||
INNER JOIN Templates d ON d.Id = c.ComposedTemplateId
|
||||
WHERE d.IsDerived = 1
|
||||
AND d.OwnerCompositionId = c.Id
|
||||
AND d.ParentTemplateId IS NOT NULL;
|
||||
|
||||
DELETE a FROM TemplateAttributes a
|
||||
INNER JOIN Templates t ON t.Id = a.TemplateId
|
||||
WHERE t.IsDerived = 1;
|
||||
|
||||
DELETE s FROM TemplateScripts s
|
||||
INNER JOIN Templates t ON t.Id = s.TemplateId
|
||||
WHERE t.IsDerived = 1;
|
||||
|
||||
DELETE FROM Templates WHERE IsDerived = 1;
|
||||
");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user