using System; using Microsoft.EntityFrameworkCore.Migrations; using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; #nullable disable namespace DeepDrftData.Migrations { /// public partial class NormalizeReleaseTrack : Migration { /// protected override void Up(MigrationBuilder migrationBuilder) { // 1. Create the release table. migrationBuilder.CreateTable( name: "release", columns: table => new { id = table.Column(type: "bigint", nullable: false) .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), title = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), artist = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), genre = table.Column(type: "character varying(100)", maxLength: 100, nullable: true), release_date = table.Column(type: "date", nullable: true), image_path = table.Column(type: "character varying(500)", maxLength: 500, nullable: true), release_type = table.Column(type: "character varying(20)", maxLength: 20, nullable: false, defaultValue: "Single"), created_by_user_id = table.Column(type: "bigint", nullable: true), created_at = table.Column(type: "timestamp with time zone", nullable: false), updated_at = table.Column(type: "timestamp with time zone", nullable: false), is_deleted = table.Column(type: "boolean", nullable: false, defaultValue: false) }, constraints: table => { table.PrimaryKey("PK_release", x => x.id); }); migrationBuilder.CreateIndex( name: "IX_release_is_deleted", table: "release", column: "is_deleted"); // 2. Add the nullable FK column to track. A fresh column (not a rename of // created_by_user_id) so existing rows start with a null release until back-filled. migrationBuilder.AddColumn( name: "release_id", table: "track", type: "bigint", nullable: true); // 3. Data migration — must run after the release table exists and release_id is added, // and before the release-cardinal columns are dropped from track (the SELECT reads them). // Create one release row per distinct (album, artist) from existing tracks, carrying // the release-cardinal fields. Tracks with a null album remain release_id = null. migrationBuilder.Sql(@" INSERT INTO release (title, artist, genre, release_date, image_path, release_type, created_by_user_id, created_at, updated_at, is_deleted) SELECT DISTINCT ON (album, artist) album, artist, genre, release_date, image_path, release_type, created_by_user_id, NOW(), NOW(), false FROM track WHERE album IS NOT NULL ORDER BY album, artist, id; "); // Back-fill the FK: match each track to the release created from its (album, artist). migrationBuilder.Sql(@" UPDATE track SET release_id = r.id FROM release r WHERE track.album = r.title AND track.artist = r.artist; "); // 4. Index + FK now that the column carries its back-filled values. migrationBuilder.CreateIndex( name: "IX_track_release_id", table: "track", column: "release_id"); migrationBuilder.AddForeignKey( name: "FK_track_release_release_id", table: "track", column: "release_id", principalTable: "release", principalColumn: "id", onDelete: ReferentialAction.SetNull); // 5. Drop the now-migrated release-cardinal columns from track. migrationBuilder.DropColumn(name: "album", table: "track"); migrationBuilder.DropColumn(name: "artist", table: "track"); migrationBuilder.DropColumn(name: "genre", table: "track"); migrationBuilder.DropColumn(name: "image_path", table: "track"); migrationBuilder.DropColumn(name: "release_date", table: "track"); migrationBuilder.DropColumn(name: "release_type", table: "track"); migrationBuilder.DropColumn(name: "created_by_user_id", table: "track"); } /// protected override void Down(MigrationBuilder migrationBuilder) { // 1. Re-add the track release-cardinal columns. artist is non-nullable with a default so // the add succeeds against existing rows before the back-fill repopulates it. migrationBuilder.AddColumn( name: "album", table: "track", type: "character varying(200)", maxLength: 200, nullable: true); migrationBuilder.AddColumn( name: "artist", table: "track", type: "character varying(200)", maxLength: 200, nullable: false, defaultValue: ""); migrationBuilder.AddColumn( name: "genre", table: "track", type: "character varying(100)", maxLength: 100, nullable: true); migrationBuilder.AddColumn( name: "image_path", table: "track", type: "character varying(500)", maxLength: 500, nullable: true); migrationBuilder.AddColumn( name: "release_date", table: "track", type: "date", nullable: true); migrationBuilder.AddColumn( name: "release_type", table: "track", type: "character varying(20)", maxLength: 20, nullable: false, defaultValue: "Single"); migrationBuilder.AddColumn( name: "created_by_user_id", table: "track", type: "bigint", nullable: true); // 2. Re-populate the track columns from the release join before the release table and FK go. migrationBuilder.Sql(@" UPDATE track SET artist = r.artist, album = r.title, genre = r.genre, release_date = r.release_date, image_path = r.image_path, release_type = r.release_type, created_by_user_id = r.created_by_user_id FROM release r WHERE track.release_id = r.id; "); // 3. Drop the FK, index, the release_id column, and the release table. migrationBuilder.DropForeignKey( name: "FK_track_release_release_id", table: "track"); migrationBuilder.DropIndex( name: "IX_track_release_id", table: "track"); migrationBuilder.DropColumn( name: "release_id", table: "track"); migrationBuilder.DropTable( name: "release"); } } }