using System; using Microsoft.EntityFrameworkCore.Migrations; #nullable disable namespace MaBibli.Api.Data.Migrations { /// /// Sort le statut de lecture et l'auteur de la table Livres. /// /// /// Ce que deviennent les données existantes. /// /// /// Livres.Statut était commun à tout le foyer. Il est rattaché à /// AjoutePar, la seule personne que la base associe au livre. Les statuts des /// livres sans AjoutePar (saisis sans identité) sont perdus : les attribuer à /// quelqu'un serait une invention, et les garder pour tout le monde serait exactement le /// défaut que cette migration corrige. Les trois valeurs sont reprises telles quelles, « À /// lire » compris : c'est ce que l'ancienne interface affichait, et le taire changerait ce /// que l'utilisateur voit. /// /// /// Livres.Auteur devient une ligne de Auteurs par valeur distincte, plus un lien /// en position 0. Le regroupement se fait ici sur une simple mise en minuscules — SQLite ne /// sait pas retirer les accents. La normalisation complète, et la fusion des variantes qu'elle /// révèle, sont faites en C# au démarrage suivant (voir ServiceRenormalisation). /// /// /// L'ordre des opérations compte : tout est recopié avant que les anciennes colonnes /// ne soient supprimées. /// public partial class StatutPersonnelEtTableAuteurs : Migration { /// protected override void Up(MigrationBuilder migrationBuilder) { // Mise à l'abri du contenu des deux colonnes condamnées. Supprimer une colonne sous // SQLite reconstruit la table : on le fait pendant que Livres n'a pas encore // d'enfants à préserver, et on recopie ensuite depuis cette table de transit. migrationBuilder.Sql( """ CREATE TABLE _RepriseLivres AS SELECT Id, Auteur, Statut FROM Livres; """); migrationBuilder.DropColumn( name: "Auteur", table: "Livres"); migrationBuilder.DropColumn( name: "Statut", table: "Livres"); migrationBuilder.AddColumn( name: "TitreNormalise", table: "Livres", type: "TEXT", nullable: false, defaultValue: ""); migrationBuilder.CreateTable( name: "Auteurs", columns: table => new { Id = table.Column(type: "INTEGER", nullable: false) .Annotation("Sqlite:Autoincrement", true), Nom = table.Column(type: "TEXT", nullable: false), NomNormalise = table.Column(type: "TEXT", nullable: false), CleRegroupement = table.Column(type: "TEXT", nullable: false) }, constraints: table => { table.PrimaryKey("PK_Auteurs", x => x.Id); }); migrationBuilder.CreateTable( name: "RapprochementsRefuses", columns: table => new { Id = table.Column(type: "INTEGER", nullable: false) .Annotation("Sqlite:Autoincrement", true), AuteurAId = table.Column(type: "INTEGER", nullable: false), AuteurBId = table.Column(type: "INTEGER", nullable: false) }, constraints: table => { table.PrimaryKey("PK_RapprochementsRefuses", x => x.Id); }); migrationBuilder.CreateTable( name: "StatutsLecture", columns: table => new { Id = table.Column(type: "INTEGER", nullable: false) .Annotation("Sqlite:Autoincrement", true), LivreId = table.Column(type: "INTEGER", nullable: false), Utilisateur = table.Column(type: "TEXT", nullable: false), Statut = table.Column(type: "INTEGER", nullable: false), DateMaj = table.Column(type: "TEXT", nullable: false) }, constraints: table => { table.PrimaryKey("PK_StatutsLecture", x => x.Id); table.ForeignKey( name: "FK_StatutsLecture_Livres_LivreId", column: x => x.LivreId, principalTable: "Livres", principalColumn: "Id", onDelete: ReferentialAction.Cascade); }); migrationBuilder.CreateTable( name: "LivreAuteurs", columns: table => new { LivreId = table.Column(type: "INTEGER", nullable: false), AuteurId = table.Column(type: "INTEGER", nullable: false), Position = table.Column(type: "INTEGER", nullable: false) }, constraints: table => { table.PrimaryKey("PK_LivreAuteurs", x => new { x.LivreId, x.AuteurId }); table.ForeignKey( name: "FK_LivreAuteurs_Auteurs_AuteurId", column: x => x.AuteurId, principalTable: "Auteurs", principalColumn: "Id", onDelete: ReferentialAction.Restrict); table.ForeignKey( name: "FK_LivreAuteurs_Livres_LivreId", column: x => x.LivreId, principalTable: "Livres", principalColumn: "Id", onDelete: ReferentialAction.Cascade); }); migrationBuilder.CreateIndex( name: "IX_Livres_TitreNormalise", table: "Livres", column: "TitreNormalise"); migrationBuilder.CreateIndex( name: "IX_Auteurs_CleRegroupement", table: "Auteurs", column: "CleRegroupement", unique: true); migrationBuilder.CreateIndex( name: "IX_Auteurs_NomNormalise", table: "Auteurs", column: "NomNormalise"); migrationBuilder.CreateIndex( name: "IX_LivreAuteurs_AuteurId", table: "LivreAuteurs", column: "AuteurId"); migrationBuilder.CreateIndex( name: "IX_RapprochementsRefuses_AuteurAId_AuteurBId", table: "RapprochementsRefuses", columns: new[] { "AuteurAId", "AuteurBId" }, unique: true); migrationBuilder.CreateIndex( name: "IX_StatutsLecture_LivreId_Utilisateur", table: "StatutsLecture", columns: new[] { "LivreId", "Utilisateur" }, unique: true); migrationBuilder.CreateIndex( name: "IX_StatutsLecture_Utilisateur", table: "StatutsLecture", column: "Utilisateur"); // ── Reprise des données depuis la table de transit ────────────────────────────── // Le titre normalisé n'est qu'approché ici (lower() de SQLite ne touche pas aux // accents) ; le passage C# du démarrage le recalculera correctement. migrationBuilder.Sql("UPDATE Livres SET TitreNormalise = lower(trim(Titre));"); // Une fiche auteur par valeur distincte, à la casse près. Le nom d'affichage retenu // est le premier dans l'ordre alphabétique : arbitraire, mais déterministe, et // l'utilisateur peut le corriger depuis n'importe quel livre. migrationBuilder.Sql( """ INSERT INTO Auteurs (Nom, NomNormalise, CleRegroupement) SELECT min(trim(Auteur)), lower(trim(Auteur)), lower(trim(Auteur)) FROM _RepriseLivres WHERE Auteur IS NOT NULL AND trim(Auteur) <> '' GROUP BY lower(trim(Auteur)); """); migrationBuilder.Sql( """ INSERT INTO LivreAuteurs (LivreId, AuteurId, Position) SELECT r.Id, a.Id, 0 FROM _RepriseLivres r JOIN Auteurs a ON a.CleRegroupement = lower(trim(r.Auteur)) JOIN Livres l ON l.Id = r.Id WHERE r.Auteur IS NOT NULL AND trim(r.Auteur) <> ''; """); // Le statut commun devient celui de la personne qui a saisi le livre. Sans AjoutePar, // il n'y a personne à qui l'attribuer : la ligne n'est pas créée. migrationBuilder.Sql( """ INSERT INTO StatutsLecture (LivreId, Utilisateur, Statut, DateMaj) SELECT l.Id, trim(l.AjoutePar), r.Statut, l.DateAjout FROM Livres l JOIN _RepriseLivres r ON r.Id = l.Id WHERE l.AjoutePar IS NOT NULL AND trim(l.AjoutePar) <> ''; """); migrationBuilder.Sql("DROP TABLE _RepriseLivres;"); } /// /// /// Retour en arrière. Une partie de l'information est perdue par nature : l'ancien /// modèle n'a qu'une case pour un auteur et une pour un statut. On y remet le premier /// auteur et le statut de AjoutePar ; les co-auteurs et les statuts des autres /// membres du foyer disparaissent. /// protected override void Down(MigrationBuilder migrationBuilder) { migrationBuilder.AddColumn( name: "Auteur", table: "Livres", type: "TEXT", nullable: true); migrationBuilder.AddColumn( name: "Statut", table: "Livres", type: "INTEGER", nullable: false, defaultValue: 0); migrationBuilder.Sql( """ UPDATE Livres SET Auteur = ( SELECT a.Nom FROM LivreAuteurs la JOIN Auteurs a ON a.Id = la.AuteurId WHERE la.LivreId = Livres.Id ORDER BY la.Position LIMIT 1); """); migrationBuilder.Sql( """ UPDATE Livres SET Statut = coalesce(( SELECT s.Statut FROM StatutsLecture s WHERE s.LivreId = Livres.Id AND s.Utilisateur = trim(Livres.AjoutePar) LIMIT 1), 0); """); migrationBuilder.DropTable( name: "LivreAuteurs"); migrationBuilder.DropTable( name: "RapprochementsRefuses"); migrationBuilder.DropTable( name: "StatutsLecture"); migrationBuilder.DropTable( name: "Auteurs"); migrationBuilder.DropIndex( name: "IX_Livres_TitreNormalise", table: "Livres"); migrationBuilder.DropColumn( name: "TitreNormalise", table: "Livres"); } } }