using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; using System.Reflection; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; using Pf2eModel.Model; using Pf2eModel.Model.Base; using Pf2eModel.Model.Base.Keywords; using Pf2eModel.Model.Base.Legacy; using Pf2eModel.Model.Base.Source; using Pf2eModel.Model.Base.Targets; using Pf2eModel.Model.Common; using Pf2eModel.Model.Common.I18n; using Pf2eModel.Model.Structure; using Pf2eModel.Model.Structure.AncestryRelation; using Pf2eModel.Model.Structure.ArchetypeRelation; using Pf2eModel.Model.Structure.BackgroundRelation; using Pf2eModel.Model.Structure.ClassRelation; using Pf2eModel.Model.Structure.DeityRelation; using Pf2eModel.Model.Structure.FeatRelation; using Pf2eModel.Model.Structure.RitualRelation; using Pf2eModel.Model.Structure.SpellRelation; namespace Pf2eModel; /// [SuppressMessage("ReSharper", "InconsistentNaming")] public partial class Pf2eDbContext { /// /// Длина названия сущности. /// public const int NameLength = 255; /// protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.UseIdentityByDefaultColumns(); // DefineEnums(modelBuilder); DefineBase(modelBuilder); DefineCommon(modelBuilder); DefineStructure(modelBuilder); DefineMethods(modelBuilder); } /// /// Устанавливает связь между сущностью и её корневой сущностью. /// /// Тип сущности. /// Генератор настроек таблицы сущности. private void BindEntity(EntityTypeBuilder entity) where T : class, IEntity { entity .HasOne(e => e.Entity) .WithOne() .HasForeignKey(e => e.Id) .IsRequired() .OnDelete(DeleteBehavior.Cascade); } /// /// Устанавливает связь между сущностью и локализованной строкой. /// /// Тип сущности. /// Генератор настроек таблицы сущности. /// Функция, выбирающая поле сущности. /// Флаг, указывающий, что локализованная строка не может быть `null`. private void BindLocaleString( EntityTypeBuilder entity, Expression> func, bool required = true ) where T : class { entity .Property(func) .HasConversion( v => v == null ? null : v.Serialize(), v => v == null ? null : LocaleString.Deserialize(v) ) .HasColumnType("jsonb") .IsRequired(required); } /// /// Определяет базовые сущности и их связи. /// /// Построитель модели. private void DefineBase(ModelBuilder modelBuilder) { modelBuilder.Entity(entity => { BindLocaleString(entity, e => e.Name); entity.HasMany(e => e.Traits).WithMany(t => t.Entities); entity.HasMany(e => e.Keywords).WithMany(t => t.Entities); // EntitySource entity .HasMany(e => e.Sources) .WithOne(e => e.Entity) .HasForeignKey(e => e.EntityId) .OnDelete(DeleteBehavior.Cascade); // EntityExternal entity .HasMany(e => e.Externals) .WithOne(e => e.Entity) .HasForeignKey(e => e.EntityId) .OnDelete(DeleteBehavior.Cascade); }); // LegacyEntity modelBuilder.Entity(entity => { BindLocaleString(entity, l => l.Name); entity.HasMany(l => l.RemasterEntities).WithMany(e => e.LegacyEntities); // LegacyEntitySource entity .HasMany(e => e.Sources) .WithOne(e => e.LegacyEntity) .HasForeignKey(e => e.LegacyEntityId) .OnDelete(DeleteBehavior.Cascade); // LegacyEntityExternal entity .HasMany(e => e.Externals) .WithOne(e => e.LegacyEntity) .HasForeignKey(e => e.LegacyEntityId) .OnDelete(DeleteBehavior.Cascade); }); // Source modelBuilder.Entity(entity => { entity.Property(e => e.Id).HasColumnName("id"); entity .HasMany(s => s.EntitySources) .WithOne(e => e.Source) .HasForeignKey(e => e.SourceId) .OnDelete(DeleteBehavior.Cascade); entity .HasMany(s => s.LegacyEntitySources) .WithOne(e => e.Source) .HasForeignKey(e => e.SourceId) .OnDelete(DeleteBehavior.Cascade); // + EntitySource }); modelBuilder.Entity(entity => { }); // External modelBuilder.Entity(entity => { entity .HasMany(t => t.EntityExternals) .WithOne(e => e.Target) .HasForeignKey(e => e.TargetId) .OnDelete(DeleteBehavior.Cascade); entity .HasMany(t => t.LegacyEntityExternals) .WithOne(e => e.Target) .HasForeignKey(e => e.TargetId) .OnDelete(DeleteBehavior.Cascade); }); modelBuilder.Entity(entity => { }); // Keywords modelBuilder.Entity(entity => { BindLocaleString(entity, k => k.Name); entity .HasMany(e => e.Aliases) .WithOne(e => e.Keyword) .HasForeignKey(e => e.KeywordId) .OnDelete(DeleteBehavior.Cascade); }); modelBuilder.Entity(entity => { }); } /// /// Определяет общие сущности и их связи. /// /// Построитель модели. private void DefineCommon(ModelBuilder modelBuilder) { modelBuilder.Entity(entity => { }); modelBuilder.Entity(entity => { }); modelBuilder.Entity(entity => { BindLocaleString(entity, t => t.Name); }); } /// /// Определяет структуру сущностей и их связи. /// /// Построитель модели. private void DefineStructure(ModelBuilder modelBuilder) { // Action modelBuilder.Entity(entity => { BindEntity(entity); }); // Ancestry modelBuilder.Entity(entity => { BindEntity(entity); entity .HasMany(e => e.AncestryBoosts) .WithOne(e => e.Ancestry) .HasForeignKey(e => e.AncestryId) .OnDelete(DeleteBehavior.Cascade); entity .HasMany(e => e.AncestryItems) .WithOne(e => e.Ancestry) .HasForeignKey(e => e.AncestryId) .OnDelete(DeleteBehavior.Cascade); entity .HasMany(e => e.AncestryLanguages) .WithOne(e => e.Ancestry) .HasForeignKey(e => e.AncestryId) .OnDelete(DeleteBehavior.Cascade); }); modelBuilder.Entity(entity => { }); modelBuilder.Entity(entity => { entity .HasOne(d => d.Item) .WithMany() .HasForeignKey(d => d.ItemId) .OnDelete(DeleteBehavior.Cascade); }); modelBuilder.Entity(entity => { entity .HasOne(d => d.Language) .WithMany() .HasForeignKey(d => d.LanguageId) .OnDelete(DeleteBehavior.Cascade); }); // Archetype modelBuilder.Entity(entity => { BindEntity(entity); entity .HasOne(a => a.DedicationFeat) .WithOne() .HasForeignKey(a => a.DedicationFeatId) .OnDelete(DeleteBehavior.Cascade); entity .HasMany(a => a.Feats) .WithMany(f => f.Archetypes) .UsingEntity( l => l.HasOne().WithMany().HasForeignKey(af => af.FeatId), r => r.HasOne().WithMany().HasForeignKey(af => af.ArchetypeId) ); }); // Background modelBuilder.Entity(entity => { BindEntity(entity); }); modelBuilder.Entity(entity => { entity .HasOne(d => d.Background) .WithMany(p => p.BackgroundBoosts) .HasForeignKey(d => d.BackgroundId) .OnDelete(DeleteBehavior.Cascade); }); modelBuilder.Entity(entity => { entity .HasOne(d => d.Background) .WithMany(p => p.BackgroundItems) .HasForeignKey(d => d.BackgroundId) .OnDelete(DeleteBehavior.Cascade); entity .HasOne(d => d.Entity) .WithMany() .HasForeignKey(d => d.EntityId) .OnDelete(DeleteBehavior.Cascade); }); modelBuilder.Entity(entity => { entity .HasOne(d => d.Background) .WithMany(p => p.BackgroundSkills) .HasForeignKey(d => d.BackgroundId) .OnDelete(DeleteBehavior.Cascade); }); // Class modelBuilder.Entity(entity => { BindEntity(entity); }); modelBuilder.Entity(entity => { entity .HasOne(d => d.Class) .WithMany(p => p.ClassKeyAbilities) .HasForeignKey(d => d.ClassId) .OnDelete(DeleteBehavior.Cascade); }); modelBuilder.Entity(entity => { entity .HasOne(d => d.Class) .WithMany(p => p.ClassProgressions) .HasForeignKey(d => d.ClassId) .OnDelete(DeleteBehavior.Cascade); entity .HasOne(d => d.Entity) .WithMany() .HasForeignKey(d => d.EntityId) .OnDelete(DeleteBehavior.SetNull); }); modelBuilder.Entity(entity => { entity .HasOne(d => d.Class) .WithMany(p => p.ClassTrainings) .HasForeignKey(d => d.ClassId) .OnDelete(DeleteBehavior.Cascade); entity .HasOne(d => d.Training) .WithMany() .HasForeignKey(d => d.TrainingId) .OnDelete(DeleteBehavior.SetNull); }); // Subclass modelBuilder.Entity(entity => { BindEntity(entity); entity .HasOne(g => g.Class) .WithMany(c => c.SubclassGroups) .HasForeignKey(g => g.ClassId) .OnDelete(DeleteBehavior.Cascade); }); modelBuilder.Entity(entity => { BindEntity(entity); BindLocaleString(entity, s => s.Additional); entity .HasOne(s => s.Group) .WithMany(g => g.Subclasses) .HasForeignKey(s => s.GroupId) .OnDelete(DeleteBehavior.Cascade); entity .HasMany(e => e.Entities) .WithMany() .UsingEntity( l => l.HasOne().WithMany().HasForeignKey(se => se.EntityId), r => r.HasOne().WithMany().HasForeignKey(se => se.SubclassId) ); entity .HasMany(e => e.Trainings) .WithOne(t => t.Subclass) .HasForeignKey(t => t.SubclassId) .OnDelete(DeleteBehavior.Cascade); }); modelBuilder.Entity(entity => { entity .HasOne(d => d.Subclass) .WithMany(p => p.Trainings) .HasForeignKey(d => d.SubclassId) .OnDelete(DeleteBehavior.Cascade); }); // Condition modelBuilder.Entity(entity => { BindEntity(entity); }); // Deity modelBuilder.Entity(entity => { BindEntity(entity); entity .HasMany(e => e.DeityAttributes) .WithOne(e => e.Deity) .HasForeignKey(e => e.DeityId) .OnDelete(DeleteBehavior.Cascade); entity .HasMany(e => e.DeityDomains) .WithOne(e => e.Deity) .HasForeignKey(e => e.DeityId) .OnDelete(DeleteBehavior.Cascade); entity .HasMany(e => e.DeitySpells) .WithOne(e => e.Deity) .HasForeignKey(e => e.DeityId) .OnDelete(DeleteBehavior.Cascade); entity .HasMany(e => e.DeityWeapons) .WithOne(e => e.Deity) .HasForeignKey(e => e.DeityId) .OnDelete(DeleteBehavior.Cascade); entity.HasMany(e => e.Skills).WithMany(); }); modelBuilder.Entity(entity => { }); modelBuilder.Entity(entity => { }); modelBuilder.Entity(entity => { entity .HasOne(d => d.Spell) .WithMany() .HasForeignKey(d => d.SpellId) .OnDelete(DeleteBehavior.Cascade); }); modelBuilder.Entity(entity => { }); // Feat modelBuilder.Entity(entity => { BindEntity(entity); entity .HasMany(e => e.FeatPrerequisites) .WithOne(e => e.Feat) .HasForeignKey(e => e.FeatId) .OnDelete(DeleteBehavior.Cascade); }); modelBuilder.Entity(entity => { }); // Heritage modelBuilder.Entity(entity => { BindEntity(entity); entity .HasOne(d => d.Ancestry) .WithMany(p => p.Heritages) .HasForeignKey(d => d.AncestryId) .OnDelete(DeleteBehavior.Cascade); }); // Spell modelBuilder.Entity(entity => { BindEntity(entity); BindLocaleString(entity, s => s.Trigger, false); BindLocaleString(entity, s => s.Target, false); BindLocaleString(entity, s => s.Cost, false); BindLocaleString(entity, s => s.Requirement, false); entity .HasMany(e => e.SpellAreas) .WithOne(e => e.Spell) .HasForeignKey(e => e.SpellId) .OnDelete(DeleteBehavior.Cascade); entity .HasMany(e => e.SpellDefenses) .WithOne(e => e.Spell) .HasForeignKey(e => e.SpellId) .OnDelete(DeleteBehavior.Cascade); }); modelBuilder.Entity(entity => { BindLocaleString(entity, a => a.Details, false); }); modelBuilder.Entity(entity => { }); // Ritual modelBuilder.Entity(entity => { BindEntity(entity); BindLocaleString(entity, s => s.RangeString, false); BindLocaleString(entity, s => s.Target, false); BindLocaleString(entity, s => s.Cost, false); BindLocaleString(entity, s => s.Requirement, false); BindLocaleString(entity, s => s.PrimaryCheckSuffix, false); BindLocaleString(entity, s => s.SecondaryCastersSuffix, false); entity .HasOne(r => r.Area) .WithOne(a => a.Ritual) .HasForeignKey(a => a.RitualId) .OnDelete(DeleteBehavior.Cascade); entity .HasMany(r => r.Checks) .WithOne(c => c.Ritual) .HasForeignKey(c => c.RitualId) .OnDelete(DeleteBehavior.Cascade); }); modelBuilder.Entity(entity => { BindLocaleString(entity, a => a.Details, false); }); modelBuilder.Entity(entity => { BindLocaleString(entity, a => a.TrainingString, false); entity .HasOne(c => c.Training) .WithMany() .HasForeignKey(c => c.TrainingId) .OnDelete(DeleteBehavior.SetNull); }); // Trait modelBuilder.Entity(entity => { BindEntity(entity); }); // Rule modelBuilder.Entity(entity => { BindEntity(entity); entity .HasOne(r => r.Parent) .WithMany(r => r.Children) .HasForeignKey(r => r.ParentId) .IsRequired(false); }); } /// /// Определяет методы, которые будут использоваться в запросах LINQ. /// /// Построитель модели. private void DefineMethods(ModelBuilder modelBuilder) { MethodInfo localeSearch = typeof(Pf2eDbContext).GetMethod( nameof(LocaleSearch), BindingFlags.Static | BindingFlags.Public, [typeof(LocaleString), typeof(string)] )!; modelBuilder.HasDbFunction( localeSearch, builder => { builder.HasName("locale_search"); builder.HasParameter("data").HasStoreType("jsonb"); builder.HasParameter("search_value"); } ); MethodInfo localeSearchArray = typeof(Pf2eDbContext).GetMethod( nameof(LocaleSearchArray), BindingFlags.Static | BindingFlags.Public, [typeof(LocaleString), typeof(IEnumerable)] )!; modelBuilder.HasDbFunction( localeSearchArray, builder => { builder.HasName("locale_search_array"); builder.HasParameter("data").HasStoreType("jsonb"); builder.HasParameter("search_values"); } ); } // ReSharper disable once InconsistentNaming public static bool LocaleSearch(LocaleString data, string search_value) => throw new InvalidOperationException("Only for LINQ translation"); // ReSharper disable once InconsistentNaming public static bool LocaleSearchArray(LocaleString data, IEnumerable search_values) => throw new InvalidOperationException("Only for LINQ translation"); }