using System; using Microsoft.EntityFrameworkCore.Migrations; using NodaTime; using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; #nullable disable namespace FictionArchive.Service.NovelService.Migrations { /// public partial class AddVolumes : Migration { /// protected override void Up(MigrationBuilder migrationBuilder) { // 1. Create the Volume table migrationBuilder.CreateTable( name: "Volume", columns: table => new { Id = table.Column(type: "bigint", nullable: false) .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), Order = table.Column(type: "integer", nullable: false), NameId = table.Column(type: "uuid", nullable: false), NovelId = table.Column(type: "bigint", nullable: false), CreatedTime = table.Column(type: "timestamp with time zone", nullable: false), LastUpdatedTime = table.Column(type: "timestamp with time zone", nullable: false) }, constraints: table => { table.PrimaryKey("PK_Volume", x => x.Id); table.ForeignKey( name: "FK_Volume_LocalizationKeys_NameId", column: x => x.NameId, principalTable: "LocalizationKeys", principalColumn: "Id", onDelete: ReferentialAction.Cascade); table.ForeignKey( name: "FK_Volume_Novels_NovelId", column: x => x.NovelId, principalTable: "Novels", principalColumn: "Id", onDelete: ReferentialAction.Cascade); }); migrationBuilder.CreateIndex( name: "IX_Volume_NameId", table: "Volume", column: "NameId"); migrationBuilder.CreateIndex( name: "IX_Volume_NovelId_Order", table: "Volume", columns: new[] { "NovelId", "Order" }, unique: true); // 2. Add nullable VolumeId column to Chapter (keep NovelId for now) migrationBuilder.AddColumn( name: "VolumeId", table: "Chapter", type: "bigint", nullable: true); // 3. Data migration: Create volumes and link chapters for each novel migrationBuilder.Sql(@" DO $$ DECLARE novel_rec RECORD; loc_key_id uuid; volume_id bigint; BEGIN FOR novel_rec IN SELECT ""Id"", ""RawLanguage"" FROM ""Novels"" LOOP -- Create LocalizationKey for volume name loc_key_id := gen_random_uuid(); INSERT INTO ""LocalizationKeys"" (""Id"", ""CreatedTime"", ""LastUpdatedTime"") VALUES (loc_key_id, NOW(), NOW()); -- Create LocalizationText for 'Main Story' in novel's raw language INSERT INTO ""LocalizationText"" (""Id"", ""LocalizationKeyId"", ""Language"", ""Text"", ""CreatedTime"", ""LastUpdatedTime"") VALUES (gen_random_uuid(), loc_key_id, novel_rec.""RawLanguage"", 'Main Story', NOW(), NOW()); -- Create Volume for this novel INSERT INTO ""Volume"" (""Order"", ""NameId"", ""NovelId"", ""CreatedTime"", ""LastUpdatedTime"") VALUES (1, loc_key_id, novel_rec.""Id"", NOW(), NOW()) RETURNING ""Id"" INTO volume_id; -- Link all chapters of this novel to the new volume UPDATE ""Chapter"" SET ""VolumeId"" = volume_id WHERE ""NovelId"" = novel_rec.""Id""; END LOOP; END $$; "); // 4. Drop old FK and index for NovelId migrationBuilder.DropForeignKey( name: "FK_Chapter_Novels_NovelId", table: "Chapter"); migrationBuilder.DropIndex( name: "IX_Chapter_NovelId", table: "Chapter"); // 5. Drop NovelId column from Chapter migrationBuilder.DropColumn( name: "NovelId", table: "Chapter"); // 6. Make VolumeId non-nullable migrationBuilder.AlterColumn( name: "VolumeId", table: "Chapter", type: "bigint", nullable: false, oldClrType: typeof(long), oldType: "bigint", oldNullable: true); // 7. Add unique index and FK for VolumeId migrationBuilder.CreateIndex( name: "IX_Chapter_VolumeId_Order", table: "Chapter", columns: new[] { "VolumeId", "Order" }, unique: true); migrationBuilder.AddForeignKey( name: "FK_Chapter_Volume_VolumeId", table: "Chapter", column: "VolumeId", principalTable: "Volume", principalColumn: "Id", onDelete: ReferentialAction.Cascade); } /// protected override void Down(MigrationBuilder migrationBuilder) { // Add back NovelId column migrationBuilder.AddColumn( name: "NovelId", table: "Chapter", type: "bigint", nullable: true); // Migrate data back: set NovelId from Volume migrationBuilder.Sql(@" UPDATE ""Chapter"" c SET ""NovelId"" = v.""NovelId"" FROM ""Volume"" v WHERE c.""VolumeId"" = v.""Id""; "); // Make NovelId non-nullable migrationBuilder.AlterColumn( name: "NovelId", table: "Chapter", type: "bigint", nullable: false, oldClrType: typeof(long), oldType: "bigint", oldNullable: true); // Drop VolumeId FK and index migrationBuilder.DropForeignKey( name: "FK_Chapter_Volume_VolumeId", table: "Chapter"); migrationBuilder.DropIndex( name: "IX_Chapter_VolumeId_Order", table: "Chapter"); // Drop VolumeId column migrationBuilder.DropColumn( name: "VolumeId", table: "Chapter"); // Recreate NovelId index and FK migrationBuilder.CreateIndex( name: "IX_Chapter_NovelId", table: "Chapter", column: "NovelId"); migrationBuilder.AddForeignKey( name: "FK_Chapter_Novels_NovelId", table: "Chapter", column: "NovelId", principalTable: "Novels", principalColumn: "Id", onDelete: ReferentialAction.Cascade); // Note: Volume LocalizationKeys are not cleaned up in Down migration // as they may have been modified. Manual cleanup may be needed. migrationBuilder.DropTable( name: "Volume"); } } }