using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Flaro.Data.Migrations
{
///
public partial class Dubletten : Migration
{
///
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn(
name: "VersionLabel",
table: "MediaFiles",
type: "TEXT",
maxLength: 64,
nullable: true);
// **Erst aufräumen, dann die Regel aufstellen.** Andersherum
// scheitert diese Migration auf jeder Datenbank, in der schon
// Dubletten stehen — und zwar beim Start im Container, wo die
// Meldung niemand sieht und der Server einfach nicht hochkommt.
//
// In SQL und nicht über die Anwendung, weil Migrationen vor allem
// anderen laufen: zu diesem Zeitpunkt gibt es noch keine Dienste.
//
// Der älteste Eintrag gewinnt. Die Kennungen sind Guid v7 und
// damit zeitlich geordnet, `MIN` liefert also den zuerst
// angelegten — den, auf den vorhandene Verläufe schon zeigen.
migrationBuilder.Sql("""
CREATE TEMP TABLE FilmDubletten AS
SELECT m.Id AS Verlierer,
(SELECT MIN(s.Id) FROM Movies s
WHERE s.LibraryId = m.LibraryId AND s.TmdbId = m.TmdbId) AS Sieger
FROM Movies m
WHERE m.TmdbId IS NOT NULL
AND m.Id <> (SELECT MIN(s.Id) FROM Movies s
WHERE s.LibraryId = m.LibraryId AND s.TmdbId = m.TmdbId);
""");
// Die Dateien zuerst — sie sind das Einzige, was sich nicht
// wiederherstellen lässt.
migrationBuilder.Sql("""
UPDATE MediaFiles
SET MovieId = (SELECT Sieger FROM FilmDubletten WHERE Verlierer = MediaFiles.MovieId)
WHERE MovieId IN (SELECT Verlierer FROM FilmDubletten);
""");
// **Sehfortschritt: erst die konfliktfreien umhängen.** Hat
// derselbe Benutzer beide Einträge angefangen, ginge das nicht —
// die Eindeutigkeitsregel auf UserItemStates verböte die zweite
// Zeile.
migrationBuilder.Sql("""
UPDATE UserItemStates
SET MovieId = (SELECT Sieger FROM FilmDubletten WHERE Verlierer = UserItemStates.MovieId)
WHERE MovieId IN (SELECT Verlierer FROM FilmDubletten)
AND NOT EXISTS (
SELECT 1 FROM UserItemStates z
WHERE z.UserId = UserItemStates.UserId
AND z.MovieId = (SELECT Sieger FROM FilmDubletten
WHERE Verlierer = UserItemStates.MovieId));
""");
// Für die übrigen gewinnt der weitere Stand — niemand will
// Sehfortschritt verlieren, nur weil zwei Einträge verschmelzen.
migrationBuilder.Sql("""
UPDATE UserItemStates
SET Position = MAX(Position, COALESCE((
SELECT a.Position FROM UserItemStates a
JOIN FilmDubletten d ON a.MovieId = d.Verlierer
WHERE a.UserId = UserItemStates.UserId AND d.Sieger = UserItemStates.MovieId
LIMIT 1), 0)),
Watched = MAX(Watched, COALESCE((
SELECT a.Watched FROM UserItemStates a
JOIN FilmDubletten d ON a.MovieId = d.Verlierer
WHERE a.UserId = UserItemStates.UserId AND d.Sieger = UserItemStates.MovieId
LIMIT 1), 0)),
PlayCount = MAX(PlayCount, COALESCE((
SELECT a.PlayCount FROM UserItemStates a
JOIN FilmDubletten d ON a.MovieId = d.Verlierer
WHERE a.UserId = UserItemStates.UserId AND d.Sieger = UserItemStates.MovieId
LIMIT 1), 0))
WHERE MovieId IN (SELECT Sieger FROM FilmDubletten);
""");
migrationBuilder.Sql(
"DELETE FROM UserItemStates WHERE MovieId IN (SELECT Verlierer FROM FilmDubletten);");
// Genres und Mitwirkende sind abgeleitet — sie entstehen beim
// nächsten Auffrischen neu. Umhängen wäre mehr Aufwand für
// dasselbe Ergebnis.
migrationBuilder.Sql(
"DELETE FROM GenreLinks WHERE MovieId IN (SELECT Verlierer FROM FilmDubletten);");
migrationBuilder.Sql(
"DELETE FROM Credits WHERE MovieId IN (SELECT Verlierer FROM FilmDubletten);");
migrationBuilder.Sql(
"DELETE FROM Movies WHERE Id IN (SELECT Verlierer FROM FilmDubletten);");
migrationBuilder.Sql("DROP TABLE FilmDubletten;");
// --- Dasselbe für Serien ---------------------------------------
migrationBuilder.Sql("""
CREATE TEMP TABLE SerienDubletten AS
SELECT x.Id AS Verlierer,
(SELECT MIN(s.Id) FROM Series s
WHERE s.LibraryId = x.LibraryId AND s.TmdbId = x.TmdbId) AS Sieger
FROM Series x
WHERE x.TmdbId IS NOT NULL
AND x.Id <> (SELECT MIN(s.Id) FROM Series s
WHERE s.LibraryId = x.LibraryId AND s.TmdbId = x.TmdbId);
""");
// Folgen, deren Platz beim Sieger frei ist, wandern mit.
migrationBuilder.Sql("""
UPDATE Episodes
SET SeriesId = (SELECT Sieger FROM SerienDubletten WHERE Verlierer = Episodes.SeriesId)
WHERE SeriesId IN (SELECT Verlierer FROM SerienDubletten)
AND NOT EXISTS (
SELECT 1 FROM Episodes e
WHERE e.SeriesId = (SELECT Sieger FROM SerienDubletten
WHERE Verlierer = Episodes.SeriesId)
AND e.SeasonNumber = Episodes.SeasonNumber
AND e.EpisodeNumber = Episodes.EpisodeNumber);
""");
// Die übrigen sind doppelt belegt: die Datei wandert zur Folge des
// Siegers, die doppelte Folge fällt weg. Sonst stünde S01E01
// zweimal in der Staffelliste.
migrationBuilder.Sql("""
UPDATE MediaFiles
SET EpisodeId = (
SELECT e.Id FROM Episodes e
JOIN Episodes alt ON alt.Id = MediaFiles.EpisodeId
JOIN SerienDubletten d ON d.Verlierer = alt.SeriesId
WHERE e.SeriesId = d.Sieger
AND e.SeasonNumber = alt.SeasonNumber
AND e.EpisodeNumber = alt.EpisodeNumber)
WHERE EpisodeId IN (
SELECT alt.Id FROM Episodes alt
JOIN SerienDubletten d ON d.Verlierer = alt.SeriesId);
""");
migrationBuilder.Sql("""
DELETE FROM UserItemStates WHERE EpisodeId IN (
SELECT alt.Id FROM Episodes alt
JOIN SerienDubletten d ON d.Verlierer = alt.SeriesId);
""");
migrationBuilder.Sql("""
DELETE FROM Episodes WHERE SeriesId IN (SELECT Verlierer FROM SerienDubletten);
""");
migrationBuilder.Sql(
"DELETE FROM GenreLinks WHERE SeriesId IN (SELECT Verlierer FROM SerienDubletten);");
migrationBuilder.Sql(
"DELETE FROM Credits WHERE SeriesId IN (SELECT Verlierer FROM SerienDubletten);");
migrationBuilder.Sql(
"DELETE FROM Series WHERE Id IN (SELECT Verlierer FROM SerienDubletten);");
migrationBuilder.Sql("DROP TABLE SerienDubletten;");
migrationBuilder.CreateIndex(
name: "IX_Series_LibraryId_TmdbId",
table: "Series",
columns: new[] { "LibraryId", "TmdbId" },
unique: true,
filter: "\"TmdbId\" IS NOT NULL");
migrationBuilder.CreateIndex(
name: "IX_Movies_LibraryId_TmdbId",
table: "Movies",
columns: new[] { "LibraryId", "TmdbId" },
unique: true,
filter: "\"TmdbId\" IS NOT NULL");
}
///
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_Series_LibraryId_TmdbId",
table: "Series");
migrationBuilder.DropIndex(
name: "IX_Movies_LibraryId_TmdbId",
table: "Movies");
migrationBuilder.DropColumn(
name: "VersionLabel",
table: "MediaFiles");
}
}
}