using Microsoft.EntityFrameworkCore; using SVSim.Database.Entities.Story; namespace SVSim.Database.Repositories.Story; public class StoryMasterRepository : IStoryMasterRepository { private readonly SVSimDbContext _db; public StoryMasterRepository(SVSimDbContext db) { _db = db; } public Task> GetSectionsByFamilyAsync(StoryApiType apiType) { // /story/section (AllStory) returns BOTH Main and Limited sections — they share world_list // entries in prod. The Story Mode UI's "Special" submenu in world 1 is populated by the // limited-story sections (section_id >= 9000, story_type_overwrite=2) appearing alongside // the Prologue (section 0). Event-story sections never appear here — they live behind their // own menu and are fetched via /event_story/section. var families = apiType == StoryApiType.AllStory ? new[] { StoryApiType.Main, StoryApiType.Limited } : new[] { apiType }; return _db.StorySections.Where(s => families.Contains(s.StoryApiType)) .OrderBy(s => s.AllStoryOrderId) .ToListAsync(); } public Task> GetWorldsForSectionsAsync(IEnumerable worldIds) => _db.StoryWorlds.Where(w => worldIds.Contains(w.Id)).ToListAsync(); public Task> GetChaptersBySectionCharaAsync(int sectionId, int charaId) => _db.StoryChapters .Include(c => c.BattleSettings).Include(c => c.Rewards).Include(c => c.SubChapters) .Where(c => c.SectionId == sectionId && c.CharaId == charaId) .ToListAsync(); // No Includes — the rollup only reads SectionId/CharaId/StoryId. Including the three owned // collections here would cartesian-explode across ~677 chapters and turn a single query into // a multi-MB result set. public Task> GetChaptersBySectionsAsync(IEnumerable sectionIds) { var ids = sectionIds.ToList(); return _db.StoryChapters .AsNoTracking() .Where(c => ids.Contains(c.SectionId)) .ToListAsync(); } public Task GetChapterByIdAsync(int storyId) => _db.StoryChapters .Include(c => c.BattleSettings).Include(c => c.Rewards).Include(c => c.SubChapters) .FirstOrDefaultAsync(c => c.StoryId == storyId); public Task GetSbsByIdAsync(int sbsId) => _db.SpecialBattleSettings.FirstOrDefaultAsync(s => s.Id == sbsId); public async Task FindSubChapterByStoryIdAsync(int storyId) { // StorySubChapter is an owned entity (no DbSet of its own); query through the owning // chapter. SelectMany over the owned collection translates to a JOIN in the relational // provider — no need to materialize the full chapter row. return await _db.StoryChapters .AsNoTracking() .SelectMany(c => c.SubChapters) .FirstOrDefaultAsync(sc => sc.SubChapterStoryId == storyId); } }