Files
Pf2eDataScrapper/Pf2eModel/Pf2eDbContextModel.cs
T

594 lines
19 KiB
C#

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.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;
/// <inheritdoc/>
[SuppressMessage("ReSharper", "InconsistentNaming")]
public partial class Pf2eDbContext
{
/// <summary>
/// Длина названия сущности.
/// </summary>
public const int NameLength = 255;
/// <inheritdoc/>
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.UseIdentityByDefaultColumns();
// DefineEnums(modelBuilder);
DefineBase(modelBuilder);
DefineCommon(modelBuilder);
DefineStructure(modelBuilder);
DefineMethods(modelBuilder);
}
/// <summary>
/// Устанавливает связь между сущностью и её корневой сущностью.
/// </summary>
/// <typeparam name="T">Тип сущности.</typeparam>
/// <param name="entity">Генератор настроек таблицы сущности.</param>
private void BindEntity<T>(EntityTypeBuilder<T> entity)
where T : class, IEntity
{
entity
.HasOne(e => e.Entity)
.WithOne()
.HasForeignKey<T>(e => e.Id)
.IsRequired()
.OnDelete(DeleteBehavior.Cascade);
}
/// <summary>
/// Устанавливает связь между сущностью и локализованной строкой.
/// </summary>
/// <typeparam name="T">Тип сущности.</typeparam>
/// <param name="entity">Генератор настроек таблицы сущности.</param>
/// <param name="func">Функция, выбирающая поле сущности.</param>
/// <param name="required">Флаг, указывающий, что локализованная строка не может быть `null`.</param>
private void BindLocaleString<T>(
EntityTypeBuilder<T> entity,
Expression<Func<T, LocaleString?>> 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);
}
/// <summary>
/// Определяет базовые сущности и их связи.
/// </summary>
/// <param name="modelBuilder">Построитель модели.</param>
private void DefineBase(ModelBuilder modelBuilder)
{
modelBuilder.Entity<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);
// EntityAliases
entity
.HasMany(e => e.Aliases)
.WithOne(a => a.Entity)
.HasForeignKey(a => a.EntityId)
.OnDelete(DeleteBehavior.Cascade);
// 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);
// LegacyRemaster
entity
.HasMany(e => e.LegacyEntities)
.WithMany(e => e.RemasterEntities)
.UsingEntity<LegacyRemaster>(
//l =>
// l.HasOne(lr => lr.LegacyEntity).WithMany().HasForeignKey(lr => lr.LegacyId),
//r =>
// r.HasOne(lr => lr.RemasterEntity)
// .WithMany()
// .HasForeignKey(lr => lr.RemasterId)
);
});
// Source
modelBuilder.Entity<Source>(entity =>
{
entity.Property(e => e.Id).HasColumnName("id");
entity
.HasMany(s => s.EntitySources)
.WithOne(e => e.Source)
.HasForeignKey(e => e.SourceId)
.OnDelete(DeleteBehavior.Cascade);
// + EntitySource
});
modelBuilder.Entity<EntitySource>(entity => { });
// External
modelBuilder.Entity<ExternalTarget>(entity =>
{
entity
.HasMany(t => t.EntityExternals)
.WithOne(e => e.Target)
.HasForeignKey(e => e.TargetId)
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity<EntityExternal>(entity => { });
// Keywords
modelBuilder.Entity<Keyword>(entity =>
{
BindLocaleString(entity, k => k.Name);
entity
.HasMany(e => e.Aliases)
.WithOne(e => e.Keyword)
.HasForeignKey(e => e.KeywordId)
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity<KeywordAlias>(entity => { });
}
/// <summary>
/// Определяет общие сущности и их связи.
/// </summary>
/// <param name="modelBuilder">Построитель модели.</param>
private void DefineCommon(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Frequency>(entity => { });
modelBuilder.Entity<Language>(entity => { });
modelBuilder.Entity<Training>(entity =>
{
BindLocaleString(entity, t => t.Name);
});
}
/// <summary>
/// Определяет структуру сущностей и их связи.
/// </summary>
/// <param name="modelBuilder">Построитель модели.</param>
private void DefineStructure(ModelBuilder modelBuilder)
{
// Action
modelBuilder.Entity<Model.Structure.Action>(entity =>
{
BindEntity(entity);
});
// Ancestry
modelBuilder.Entity<Ancestry>(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<AncestryBoost>(entity => { });
modelBuilder.Entity<AncestryItem>(entity =>
{
entity
.HasOne(d => d.Item)
.WithMany()
.HasForeignKey(d => d.ItemId)
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity<AncestryLanguage>(entity =>
{
entity
.HasOne(d => d.Language)
.WithMany()
.HasForeignKey(d => d.LanguageId)
.OnDelete(DeleteBehavior.Cascade);
});
// Archetype
modelBuilder.Entity<Archetype>(entity =>
{
BindEntity(entity);
entity
.HasOne(a => a.DedicationFeat)
.WithOne()
.HasForeignKey<Archetype>(a => a.DedicationFeatId)
.OnDelete(DeleteBehavior.Cascade);
entity
.HasMany(a => a.Feats)
.WithMany(f => f.Archetypes)
.UsingEntity<ArchetypeFeats>(
l => l.HasOne<Feat>().WithMany().HasForeignKey(af => af.FeatId),
r => r.HasOne<Archetype>().WithMany().HasForeignKey(af => af.ArchetypeId)
);
});
// Background
modelBuilder.Entity<Background>(entity =>
{
BindEntity(entity);
});
modelBuilder.Entity<BackgroundBoost>(entity =>
{
entity
.HasOne(d => d.Background)
.WithMany(p => p.BackgroundBoosts)
.HasForeignKey(d => d.BackgroundId)
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity<BackgroundItem>(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<BackgroundTraining>(entity =>
{
entity
.HasOne(d => d.Background)
.WithMany(p => p.BackgroundSkills)
.HasForeignKey(d => d.BackgroundId)
.OnDelete(DeleteBehavior.Cascade);
});
// Class
modelBuilder.Entity<Class>(entity =>
{
BindEntity(entity);
});
modelBuilder.Entity<ClassKeyAbility>(entity =>
{
entity
.HasOne(d => d.Class)
.WithMany(p => p.ClassKeyAbilities)
.HasForeignKey(d => d.ClassId)
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity<ClassProgression>(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<ClassTraining>(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<SubclassGroup>(entity =>
{
BindEntity(entity);
entity
.HasOne(g => g.Class)
.WithMany(c => c.SubclassGroups)
.HasForeignKey(g => g.ClassId)
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity<Subclass>(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<SubclassProvidedEntities>(
l => l.HasOne<Entity>().WithMany().HasForeignKey(se => se.EntityId),
r => r.HasOne<Subclass>().WithMany().HasForeignKey(se => se.SubclassId)
);
entity
.HasMany(e => e.Trainings)
.WithOne(t => t.Subclass)
.HasForeignKey(t => t.SubclassId)
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity<SubclassTrainings>(entity =>
{
entity
.HasOne(d => d.Subclass)
.WithMany(p => p.Trainings)
.HasForeignKey(d => d.SubclassId)
.OnDelete(DeleteBehavior.Cascade);
});
// Condition
modelBuilder.Entity<Condition>(entity =>
{
BindEntity(entity);
});
// Deity
modelBuilder.Entity<Deity>(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<DeityAttribute>(entity => { });
modelBuilder.Entity<DeityDomain>(entity => { });
modelBuilder.Entity<DeitySpell>(entity =>
{
entity
.HasOne(d => d.Spell)
.WithMany()
.HasForeignKey(d => d.SpellId)
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity<DeityWeapon>(entity => { });
// Feat
modelBuilder.Entity<Feat>(entity =>
{
BindEntity(entity);
entity
.HasMany(e => e.FeatPrerequisites)
.WithOne(e => e.Feat)
.HasForeignKey(e => e.FeatId)
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity<FeatPrerequisite>(entity => { });
// Heritage
modelBuilder.Entity<Heritage>(entity =>
{
BindEntity(entity);
entity
.HasOne(d => d.Ancestry)
.WithMany(p => p.Heritages)
.HasForeignKey(d => d.AncestryId)
.OnDelete(DeleteBehavior.Cascade);
});
// Spell
modelBuilder.Entity<Spell>(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<SpellArea>(entity =>
{
BindLocaleString(entity, a => a.Details, false);
});
modelBuilder.Entity<SpellDefense>(entity => { });
// Ritual
modelBuilder.Entity<Ritual>(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<RitualArea>(a => a.RitualId)
.OnDelete(DeleteBehavior.Cascade);
entity
.HasMany(r => r.Checks)
.WithOne(c => c.Ritual)
.HasForeignKey(c => c.RitualId)
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity<RitualArea>(entity =>
{
BindLocaleString(entity, a => a.Details, false);
});
modelBuilder.Entity<RitualCheck>(entity =>
{
BindLocaleString(entity, a => a.TrainingString, false);
entity
.HasOne(c => c.Training)
.WithMany()
.HasForeignKey(c => c.TrainingId)
.OnDelete(DeleteBehavior.SetNull);
});
// Trait
modelBuilder.Entity<Trait>(entity =>
{
BindEntity(entity);
});
// Rule
modelBuilder.Entity<Rule>(entity =>
{
BindEntity(entity);
entity
.HasOne(r => r.Parent)
.WithMany(r => r.Children)
.HasForeignKey(r => r.ParentId)
.IsRequired(false);
});
}
/// <summary>
/// Определяет методы, которые будут использоваться в запросах LINQ.
/// </summary>
/// <param name="modelBuilder">Построитель модели.</param>
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<string>)]
)!;
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<string> search_values) =>
throw new InvalidOperationException("Only for LINQ translation");
}