A character which has participated in a mini game with enabled ranking statistics could not be deleted. The database rejected it with Npgsql.PostgresException 23503: update or delete on table "Character" violates foreign key constraint "FK_MiniGameRankingEntry_Character_CharacterId" MiniGameRankingEntry is a member of neither the character nor the mini game definition aggregate, so the model generator doesn't emit a delete behavior for its references. The entity was registered without any additional configuration, so Entity Framework fell back to no action for these optional relationships, which the database translates into a restricting foreign key. Both references are now configured with a cascading delete, matching the behavior of the other references to a character. With that, all seven relationships pointing to Character delete their dependents. This affects both deletion paths: the one of the player (DeleteCharacterAction) and the one of an administrator, who can remove a character from the account in the admin panel. The cascade on the mini game definition is deliberate and part of the same defect: deleting a definition in the admin panel ran into the very same restriction. It does mean that the ranking history of a mini game is dropped together with its definition. Reported in issue 796. (cherry picked from commit a391bdedbbd3a36ccb37124125bd7ede4f851dca)
101 lines
4.3 KiB
C#
101 lines
4.3 KiB
C#
// <copyright file="EntityDataContext.cs" company="MUnique">
|
|
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
|
// </copyright>
|
|
|
|
namespace MUnique.OpenMU.Persistence.EntityFramework;
|
|
|
|
using Microsoft.EntityFrameworkCore;
|
|
using MUnique.OpenMU.AttributeSystem;
|
|
using MUnique.OpenMU.Persistence.EntityFramework.Extensions;
|
|
using MUnique.OpenMU.Persistence.EntityFramework.Extensions.ModelBuilder;
|
|
using MUnique.OpenMU.Persistence.EntityFramework.Model;
|
|
|
|
/// <summary>
|
|
/// Context for all types of the data model.
|
|
/// </summary>
|
|
public class EntityDataContext : ExtendedTypeContext
|
|
{
|
|
/// <summary>
|
|
/// Gets or sets the current game configuration.
|
|
/// This is used by the <see cref="ConfigurationTypeRepository{T}"/> which gets its data from the current game configuration.
|
|
/// </summary>
|
|
internal GameConfiguration? CurrentGameConfiguration { get; set; }
|
|
|
|
/// <inheritdoc/>
|
|
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
|
{
|
|
if (!ConnectionConfigurator.IsInitialized)
|
|
{
|
|
ConnectionConfigurator.Initialize(new ConfigFileDatabaseConnectionStringProvider());
|
|
}
|
|
|
|
base.OnConfiguring(optionsBuilder);
|
|
this.Configure(optionsBuilder);
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
|
{
|
|
base.OnModelCreating(modelBuilder);
|
|
|
|
modelBuilder.Entity<AppearanceData>(o => o.Ignore(p => p.CharacterStatus)); // todo
|
|
modelBuilder.Ignore<ConstantElement>();
|
|
modelBuilder.Ignore<SimpleElement>();
|
|
modelBuilder.Entity<Model.AttributeDefinition>();
|
|
modelBuilder.Entity<ConnectServerDefinition>();
|
|
modelBuilder.Entity<ChatServerDefinition>();
|
|
modelBuilder.Entity<MiniGameRankingEntry>().Apply();
|
|
modelBuilder.Entity<GameServerDefinition>(entity =>
|
|
{
|
|
entity.Property(e => e.PvpEnabled).HasDefaultValue(true);
|
|
});
|
|
modelBuilder.Entity<ConfigurationUpdate>().Apply();
|
|
modelBuilder.Entity<ConfigurationUpdateState>();
|
|
modelBuilder.Entity<SystemConfiguration>();
|
|
|
|
modelBuilder.Entity<PowerUpDefinitionValue>().Apply();
|
|
modelBuilder.Entity<Model.ConstValueAttribute>().Apply();
|
|
modelBuilder.Entity<Account>().Apply();
|
|
modelBuilder.Entity<Character>().Apply();
|
|
modelBuilder.Entity<CharacterClass>().Apply();
|
|
modelBuilder.Entity<DropItemGroup>().Apply();
|
|
modelBuilder.Entity<ExitGate>().Apply();
|
|
modelBuilder.Entity<GameConfiguration>().Apply();
|
|
modelBuilder.Entity<GameMapDefinition>().Apply();
|
|
modelBuilder.Entity<ItemCrafting>().Apply();
|
|
modelBuilder.Entity<ItemDefinition>().Apply();
|
|
modelBuilder.Entity<ItemLevelBonusTable>().Apply();
|
|
modelBuilder.Entity<ItemDropItemGroup>().Apply();
|
|
modelBuilder.Entity<ItemOptionCombinationBonus>().Apply();
|
|
modelBuilder.Entity<ItemOptionDefinition>().Apply();
|
|
modelBuilder.Entity<ItemOptionType>().Apply();
|
|
modelBuilder.Entity<ItemSetGroup>().Apply();
|
|
modelBuilder.Entity<ItemSlotType>().Apply();
|
|
modelBuilder.Entity<ItemStorage>().Apply();
|
|
modelBuilder.Entity<ItemBasePowerUpDefinition>().Apply();
|
|
modelBuilder.Entity<LevelBonus>().Apply();
|
|
modelBuilder.Entity<MagicEffectDefinition>().Apply();
|
|
modelBuilder.Entity<MasterSkillRoot>().Apply();
|
|
modelBuilder.Entity<MiniGameChangeEvent>().Apply();
|
|
modelBuilder.Entity<MiniGameDefinition>().Apply();
|
|
modelBuilder.Entity<MiniGameSpawnWave>().Apply();
|
|
modelBuilder.Entity<MonsterDefinition>().Apply();
|
|
modelBuilder.Entity<MonsterSpawnArea>().Apply();
|
|
modelBuilder.Entity<Skill>().Apply();
|
|
modelBuilder.Entity<SkillComboDefinition>().Apply();
|
|
modelBuilder.Entity<SkillEntry>().Apply();
|
|
modelBuilder.Entity<MasterSkillDefinition>().Apply();
|
|
modelBuilder.Entity<LetterBody>().Apply();
|
|
modelBuilder.Entity<LetterHeader>().Apply();
|
|
modelBuilder.Entity<QuestDefinition>().Apply();
|
|
modelBuilder.Entity<WarpInfo>().Apply();
|
|
|
|
// join entity keys:
|
|
this.AddJoinDefinitions(modelBuilder);
|
|
|
|
modelBuilder.UseGuidV7Ids();
|
|
|
|
GuildContext.ConfigureModel(modelBuilder);
|
|
FriendContext.ConfigureModel(modelBuilder);
|
|
}
|
|
} |