refactor: simplify data connections from many-to-many site assignment to direct site ownership
Replace SiteDataConnectionAssignment join table with a direct SiteId FK on DataConnection, simplifying the data model, repositories, UI, CLI, and deployment service.
This commit is contained in:
1227
src/ScadaLink.ConfigurationDatabase/Migrations/20260321210402_AddSiteIdToDataConnections.Designer.cs
generated
Normal file
1227
src/ScadaLink.ConfigurationDatabase/Migrations/20260321210402_AddSiteIdToDataConnections.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,184 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace ScadaLink.ConfigurationDatabase.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddSiteIdToDataConnections : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
// Step 1: Drop old unique index on Name (allows duplicate names across sites)
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_DataConnections_Name",
|
||||
table: "DataConnections");
|
||||
|
||||
// Step 2: Add nullable SiteId column
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "SiteId",
|
||||
table: "DataConnections",
|
||||
type: "int",
|
||||
nullable: true);
|
||||
|
||||
// Step 3: Migrate data from SiteDataConnectionAssignments
|
||||
migrationBuilder.Sql(@"
|
||||
-- Phase A: Assign the first site to each existing DataConnection
|
||||
UPDATE dc
|
||||
SET dc.SiteId = a.SiteId
|
||||
FROM DataConnections dc
|
||||
INNER JOIN (
|
||||
SELECT DataConnectionId, MIN(SiteId) AS SiteId
|
||||
FROM SiteDataConnectionAssignments
|
||||
GROUP BY DataConnectionId
|
||||
) a ON dc.Id = a.DataConnectionId
|
||||
WHERE dc.SiteId IS NULL;
|
||||
|
||||
-- Phase B: For connections assigned to additional sites, create copies
|
||||
-- and update InstanceConnectionBindings to point to the new copy
|
||||
DECLARE @AssignSiteId INT, @AssignConnId INT, @NewConnId INT;
|
||||
DECLARE @OrigName NVARCHAR(200), @OrigProtocol NVARCHAR(50), @OrigConfig NVARCHAR(4000);
|
||||
|
||||
DECLARE assignment_cursor CURSOR FOR
|
||||
SELECT a.SiteId, a.DataConnectionId
|
||||
FROM SiteDataConnectionAssignments a
|
||||
INNER JOIN DataConnections dc ON a.DataConnectionId = dc.Id
|
||||
WHERE dc.SiteId <> a.SiteId;
|
||||
|
||||
OPEN assignment_cursor;
|
||||
FETCH NEXT FROM assignment_cursor INTO @AssignSiteId, @AssignConnId;
|
||||
|
||||
WHILE @@FETCH_STATUS = 0
|
||||
BEGIN
|
||||
SELECT @OrigName = Name, @OrigProtocol = Protocol, @OrigConfig = Configuration
|
||||
FROM DataConnections WHERE Id = @AssignConnId;
|
||||
|
||||
INSERT INTO DataConnections (SiteId, Name, Protocol, Configuration)
|
||||
VALUES (@AssignSiteId, @OrigName, @OrigProtocol, @OrigConfig);
|
||||
|
||||
SET @NewConnId = SCOPE_IDENTITY();
|
||||
|
||||
-- Update bindings for instances on this site to point to the new connection
|
||||
UPDATE icb
|
||||
SET icb.DataConnectionId = @NewConnId
|
||||
FROM InstanceConnectionBindings icb
|
||||
INNER JOIN Instances i ON icb.InstanceId = i.Id
|
||||
WHERE icb.DataConnectionId = @AssignConnId
|
||||
AND i.SiteId = @AssignSiteId;
|
||||
|
||||
FETCH NEXT FROM assignment_cursor INTO @AssignSiteId, @AssignConnId;
|
||||
END
|
||||
|
||||
CLOSE assignment_cursor;
|
||||
DEALLOCATE assignment_cursor;
|
||||
|
||||
-- Phase C: Handle any DataConnections not assigned to any site
|
||||
-- (assign to the first site as a fallback)
|
||||
UPDATE dc
|
||||
SET dc.SiteId = (SELECT TOP 1 Id FROM Sites ORDER BY Id)
|
||||
FROM DataConnections dc
|
||||
WHERE dc.SiteId IS NULL;
|
||||
");
|
||||
|
||||
// Step 4: Make SiteId non-nullable
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "SiteId",
|
||||
table: "DataConnections",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
defaultValue: 0,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int",
|
||||
oldNullable: true);
|
||||
|
||||
// Step 5: Add composite unique index and FK
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_DataConnections_SiteId_Name",
|
||||
table: "DataConnections",
|
||||
columns: new[] { "SiteId", "Name" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_DataConnections_Sites_SiteId",
|
||||
table: "DataConnections",
|
||||
column: "SiteId",
|
||||
principalTable: "Sites",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
|
||||
// Step 6: Drop SiteDataConnectionAssignments table
|
||||
migrationBuilder.DropTable(
|
||||
name: "SiteDataConnectionAssignments");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
// Recreate SiteDataConnectionAssignments table
|
||||
migrationBuilder.CreateTable(
|
||||
name: "SiteDataConnectionAssignments",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "int", nullable: false)
|
||||
.Annotation("SqlServer:Identity", "1, 1"),
|
||||
DataConnectionId = table.Column<int>(type: "int", nullable: false),
|
||||
SiteId = table.Column<int>(type: "int", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_SiteDataConnectionAssignments", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_SiteDataConnectionAssignments_DataConnections_DataConnectionId",
|
||||
column: x => x.DataConnectionId,
|
||||
principalTable: "DataConnections",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_SiteDataConnectionAssignments_Sites_SiteId",
|
||||
column: x => x.SiteId,
|
||||
principalTable: "Sites",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_SiteDataConnectionAssignments_DataConnectionId",
|
||||
table: "SiteDataConnectionAssignments",
|
||||
column: "DataConnectionId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_SiteDataConnectionAssignments_SiteId_DataConnectionId",
|
||||
table: "SiteDataConnectionAssignments",
|
||||
columns: new[] { "SiteId", "DataConnectionId" },
|
||||
unique: true);
|
||||
|
||||
// Migrate data back
|
||||
migrationBuilder.Sql(@"
|
||||
INSERT INTO SiteDataConnectionAssignments (SiteId, DataConnectionId)
|
||||
SELECT SiteId, Id FROM DataConnections;
|
||||
");
|
||||
|
||||
// Remove FK and composite index
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_DataConnections_Sites_SiteId",
|
||||
table: "DataConnections");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_DataConnections_SiteId_Name",
|
||||
table: "DataConnections");
|
||||
|
||||
// Restore unique index on Name
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_DataConnections_Name",
|
||||
table: "DataConnections",
|
||||
column: "Name",
|
||||
unique: true);
|
||||
|
||||
// Drop SiteId column
|
||||
migrationBuilder.DropColumn(
|
||||
name: "SiteId",
|
||||
table: "DataConnections");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -766,9 +766,12 @@ namespace ScadaLink.ConfigurationDatabase.Migrations
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)");
|
||||
|
||||
b.Property<int>("SiteId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Name")
|
||||
b.HasIndex("SiteId", "Name")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("DataConnections");
|
||||
@@ -821,30 +824,6 @@ namespace ScadaLink.ConfigurationDatabase.Migrations
|
||||
b.ToTable("Sites");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ScadaLink.Commons.Entities.Sites.SiteDataConnectionAssignment", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<int>("DataConnectionId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("SiteId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("DataConnectionId");
|
||||
|
||||
b.HasIndex("SiteId", "DataConnectionId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("SiteDataConnectionAssignments");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ScadaLink.Commons.Entities.Templates.Template", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -1153,18 +1132,12 @@ namespace ScadaLink.ConfigurationDatabase.Migrations
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ScadaLink.Commons.Entities.Sites.SiteDataConnectionAssignment", b =>
|
||||
modelBuilder.Entity("ScadaLink.Commons.Entities.Sites.DataConnection", b =>
|
||||
{
|
||||
b.HasOne("ScadaLink.Commons.Entities.Sites.DataConnection", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("DataConnectionId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("ScadaLink.Commons.Entities.Sites.Site", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("SiteId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user