Characters that have a MiniGameRankingEntry cannot be deleted (FK has no cascade)
Description
Deleting a character fails when that character has any row in MiniGameRankingEntry.
It happens both via the Admin Panel and in‑game (the save on
Player.RemoveFromGameAsync → SaveProgressAsync throws), with:
Npgsql.PostgresException 23503: update or delete on table "Character"
violates foreign key constraint "FK_MiniGameRankingEntry_Character_CharacterId"
on table "MiniGameRankingEntry"
Root cause
MiniGameRankingEntry.Character is an optional navigation:
// src/DataModel/Statistics/MiniGameRankingEntry.cs
public virtual Character? Character { get; set; }
and the entity is registered without any relationship/delete configuration:
// src/Persistence/EntityFramework/EntityDataContext.cs
modelBuilder.Entity<MiniGameRankingEntry>();
For an optional relationship EF Core defaults the FK to NO ACTION, so the database
rejects the character delete. Every other character‑owned relationship is explicitly
cascaded (see CharacterExtensions.cs: letters, skills, stat attributes, quest states,
drop‑item‑groups, guild membership all use OnDelete(DeleteBehavior.Cascade)).
MiniGameRankingEntry was simply missed — so it is the only one of the seven
Character foreign keys that blocks deletion.
Impact
Any character that ever placed in a mini‑game ranking (Blood Castle / Devil Square /
Chaos Castle, etc.) can no longer be deleted, by admins or players.
Reproduce
- Play a mini‑game with a character so it gets a
MiniGameRankingEntry.
- Try to delete that character (Admin Panel or in‑game) → FK violation
23503.
Two possible fixes (design decision)
-
A — Cascade (simplest, consistent). Configure the relationship with
OnDelete(DeleteBehavior.Cascade) so ranking entries are removed together with the
character. Matches how all other character‑owned data behaves. Ranking entries are
scoped per game instance (GameInstanceId), i.e. transient, so dropping them on
character deletion is reasonable.
-
B — Keep history (SET NULL + snapshot). If ranking entries should survive
character deletion (a persistent leaderboard), change the FK to SetNull (the
navigation is already nullable) and store a denormalized character‑name snapshot on
the entry, updating the ranking view to use it. More involved; only worth it if
persistent leaderboards are intended. The current NO ACTION implements neither
intent — it just blocks deletion.
Note on existing databases
A model/config change only fixes new schemas. Already‑created databases also need a
migration (or a manual ALTER TABLE data."MiniGameRankingEntry" ... ON DELETE CASCADE/SET NULL), since EF won't retroactively alter an existing FK.
Immediate workaround
Delete the character's ranking rows first, then delete the character:
DELETE FROM data."MiniGameRankingEntry" WHERE "CharacterId" = '<character-id>';
or apply the cascade once at the DB level:
ALTER TABLE data."MiniGameRankingEntry" DROP CONSTRAINT "FK_MiniGameRankingEntry_Character_CharacterId";
ALTER TABLE data."MiniGameRankingEntry" ADD CONSTRAINT "FK_MiniGameRankingEntry_Character_CharacterId"
FOREIGN KEY ("CharacterId") REFERENCES data."Character"("Id") ON DELETE CASCADE;
Characters that have a
MiniGameRankingEntrycannot be deleted (FK has no cascade)Description
Deleting a character fails when that character has any row in
MiniGameRankingEntry.It happens both via the Admin Panel and in‑game (the save on
Player.RemoveFromGameAsync→SaveProgressAsyncthrows), with:Root cause
MiniGameRankingEntry.Characteris an optional navigation:and the entity is registered without any relationship/delete configuration:
For an optional relationship EF Core defaults the FK to
NO ACTION, so the databaserejects the character delete. Every other character‑owned relationship is explicitly
cascaded (see
CharacterExtensions.cs: letters, skills, stat attributes, quest states,drop‑item‑groups, guild membership all use
OnDelete(DeleteBehavior.Cascade)).MiniGameRankingEntrywas simply missed — so it is the only one of the sevenCharacterforeign keys that blocks deletion.Impact
Any character that ever placed in a mini‑game ranking (Blood Castle / Devil Square /
Chaos Castle, etc.) can no longer be deleted, by admins or players.
Reproduce
MiniGameRankingEntry.23503.Two possible fixes (design decision)
A — Cascade (simplest, consistent). Configure the relationship with
OnDelete(DeleteBehavior.Cascade)so ranking entries are removed together with thecharacter. Matches how all other character‑owned data behaves. Ranking entries are
scoped per game instance (
GameInstanceId), i.e. transient, so dropping them oncharacter deletion is reasonable.
B — Keep history (
SET NULL+ snapshot). If ranking entries should survivecharacter deletion (a persistent leaderboard), change the FK to
SetNull(thenavigation is already nullable) and store a denormalized character‑name snapshot on
the entry, updating the ranking view to use it. More involved; only worth it if
persistent leaderboards are intended. The current
NO ACTIONimplements neitherintent — it just blocks deletion.
Note on existing databases
A model/config change only fixes new schemas. Already‑created databases also need a
migration (or a manual
ALTER TABLE data."MiniGameRankingEntry" ... ON DELETE CASCADE/SET NULL), since EF won't retroactively alter an existing FK.Immediate workaround
Delete the character's ranking rows first, then delete the character:
or apply the cascade once at the DB level: