using Microsoft.EntityFrameworkCore.Migrations; #nullable disable namespace ScadaLink.ConfigurationDatabase.Migrations { /// public partial class AddSiteIdToDataConnections : Migration { /// 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( 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( 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"); } /// protected override void Down(MigrationBuilder migrationBuilder) { // Recreate SiteDataConnectionAssignments table migrationBuilder.CreateTable( name: "SiteDataConnectionAssignments", columns: table => new { Id = table.Column(type: "int", nullable: false) .Annotation("SqlServer:Identity", "1, 1"), DataConnectionId = table.Column(type: "int", nullable: false), SiteId = table.Column(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"); } } }