using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using Microsoft.EntityFrameworkCore; using ACE.Common; using ACE.Database.Adapter; using ACE.Database.Models.World; using ACE.Database.Extensions; using ACE.Entity.Enum; using ACE.Entity.Enum.Properties; namespace ACE.Database { public class WorldDatabaseWithEntityCache : WorldDatabase { // ===================================== // Weenie // ===================================== private readonly ConcurrentDictionary weenieCache = new ConcurrentDictionary(); private readonly ConcurrentDictionary weenieClassNameToClassIdCache = new ConcurrentDictionary(); /// /// This will populate all sub collections except the following: LandblockInstances, PointsOfInterest /// This will also update the weenie cache. /// public override Weenie GetWeenie(WorldDbContext context, uint weenieClassId) { var weenie = base.GetWeenie(context, weenieClassId); // If the weenie doesn't exist in the cache, we'll add it. if (weenie != null) { weenieCache[weenieClassId] = WeenieConverter.ConvertToEntityWeenie(weenie); weenieClassNameToClassIdCache[weenie.ClassName.ToLower()] = weenie.ClassId; } else weenieCache[weenieClassId] = null; return weenie; } /// /// This will populate all sub collections except the following: LandblockInstances, PointsOfInterest /// This will also update the weenie cache. /// public override List GetAllWeenies() { var weenies = base.GetAllWeenies(); // Add the weenies to the cache foreach (var weenie in weenies) { weenieCache[weenie.ClassId] = WeenieConverter.ConvertToEntityWeenie(weenie); weenieClassNameToClassIdCache[weenie.ClassName.ToLower()] = weenie.ClassId; } return weenies; } /// /// This will make sure every weenie in the database has been read and cached. /// This function may take 10+ seconds to complete. /// public void CacheAllWeenies() { GetAllWeenies(); PopulateWeenieSpecificCaches(); } /// /// Returns the number of weenies currently cached. /// public int GetWeenieCacheCount() { return weenieCache.Count(r => r.Value != null); } public void ClearWeenieCache() { weenieCache.Clear(); weenieClassNameToClassIdCache.Clear(); weenieSpecificCachesPopulated = false; } /// /// Weenies will have all their collections populated except the following: LandblockInstances, PointsOfInterest /// public ACE.Entity.Models.Weenie GetCachedWeenie(uint weenieClassId) { if (weenieCache.TryGetValue(weenieClassId, out var value)) return value; GetWeenie(weenieClassId); // This will add the result into the caches weenieCache.TryGetValue(weenieClassId, out value); return value; } /// /// Weenies will have all their collections populated except the following: LandblockInstances, PointsOfInterest /// public ACE.Entity.Models.Weenie GetCachedWeenie(string weenieClassName) { if (weenieClassNameToClassIdCache.TryGetValue(weenieClassName.ToLower(), out var value)) return GetCachedWeenie(value); // This will add the result into the caches GetWeenie(weenieClassName); // This will add the result into the caches weenieClassNameToClassIdCache.TryGetValue(weenieClassName.ToLower(), out value); return GetCachedWeenie(value); // This will add the result into the caches } public bool ClearCachedWeenie(uint weenieClassId) { return weenieCache.TryRemove(weenieClassId, out _); } private bool weenieSpecificCachesPopulated; private readonly ConcurrentDictionary> weenieCacheByType = new ConcurrentDictionary>(); private readonly Dictionary scrollsBySpellID = new Dictionary(); private void PopulateWeenieSpecificCaches() { // populate weenieCacheByType foreach (var weenie in weenieCache.Values) { if (weenie == null) continue; if (!weenieCacheByType.TryGetValue(weenie.WeenieType, out var weenies)) { weenies = new List(); weenieCacheByType[weenie.WeenieType] = weenies; } if (!weenies.Contains(weenie)) weenies.Add(weenie); } // populate scrollsBySpellID foreach (var weenie in weenieCache.Values) { if (weenie == null) continue; if (weenie.WeenieType == WeenieType.Scroll) { if (weenie.PropertiesDID.TryGetValue(PropertyDataId.Spell, out var value)) scrollsBySpellID[value] = weenie; } } weenieSpecificCachesPopulated = true; } public List GetRandomWeeniesOfType(int weenieTypeId, int count) { if (!weenieCacheByType.TryGetValue((WeenieType)weenieTypeId, out var weenies)) { if (!weenieSpecificCachesPopulated) { using (var context = new WorldDbContext()) { var results = context.Weenie .AsNoTracking() .Where(r => r.Type == weenieTypeId) .ToList(); weenies = new List(); if (results.Count == 0) return weenies; for (int i = 0; i < count; i++) { var index = ThreadSafeRandom.Next(0, results.Count - 1); var weenie = GetCachedWeenie(results[index].ClassId); weenies.Add(weenie); } return weenies; } } weenies = new List(); weenieCacheByType[(WeenieType)weenieTypeId] = weenies; } if (weenies.Count == 0) return new List(); { var results = new List(); for (int i = 0; i < count; i++) { var index = ThreadSafeRandom.Next(0, weenies.Count - 1); var weenie = GetCachedWeenie(weenies[index].WeenieClassId); results.Add(weenie); } return results; } } public ACE.Entity.Models.Weenie GetScrollWeenie(uint spellID) { if (!scrollsBySpellID.TryGetValue(spellID, out var weenie)) { if (!weenieSpecificCachesPopulated) { using (var context = new WorldDbContext()) { var query = from weenieRecord in context.Weenie join did in context.WeeniePropertiesDID on weenieRecord.ClassId equals did.ObjectId where weenieRecord.Type == (int)WeenieType.Scroll && did.Type == (ushort)PropertyDataId.Spell && did.Value == spellID select weenieRecord; var result = query.FirstOrDefault(); if (result == null) return null; weenie = WeenieConverter.ConvertToEntityWeenie(result); scrollsBySpellID[spellID] = weenie; } } } return weenie; } private readonly ConcurrentDictionary creatureWeenieNamesLowerInvariantCache = new ConcurrentDictionary(); public bool IsCreatureNameInWorldDatabase(string name) { if (creatureWeenieNamesLowerInvariantCache.TryGetValue(name.ToLowerInvariant(), out _)) return true; using (var context = new WorldDbContext()) { return IsCreatureNameInWorldDatabase(context, name); } } public bool IsCreatureNameInWorldDatabase(WorldDbContext context, string name) { var query = from weenieRecord in context.Weenie join stringProperty in context.WeeniePropertiesString on weenieRecord.ClassId equals stringProperty.ObjectId where weenieRecord.Type == (int)WeenieType.Creature && stringProperty.Type == (ushort)PropertyString.Name && stringProperty.Value == name select weenieRecord; var weenie = query .Include(r => r.WeeniePropertiesString) .AsNoTracking() .FirstOrDefault(); if (weenie == null) return false; var weenieName = weenie.GetProperty(PropertyString.Name).ToLowerInvariant(); creatureWeenieNamesLowerInvariantCache.TryAdd(weenieName, weenie.ClassId); return true; } // ===================================== // CookBook // ===================================== private readonly Dictionary> cookbookCache = new Dictionary>(); private readonly Dictionary recipeCache = new Dictionary(); public override CookBook GetCookbook(WorldDbContext context, uint sourceWeenieClassId, uint targetWeenieClassId) { var cookbook = base.GetCookbook(context, sourceWeenieClassId, targetWeenieClassId); lock (cookbookCache) { // We double check before commiting the recipe. // We could be in this lock, and queued up behind us is an attempt to add a result for the same source:target pair. if (cookbookCache.TryGetValue(sourceWeenieClassId, out var sourceRecipes)) { if (!sourceRecipes.ContainsKey(targetWeenieClassId)) sourceRecipes.Add(targetWeenieClassId, cookbook); } else cookbookCache.Add(sourceWeenieClassId, new Dictionary() { { targetWeenieClassId, cookbook } }); } if (cookbook != null) { // build secondary index for RecipeManager_New caching lock (recipeCache) { if (!recipeCache.ContainsKey(cookbook.RecipeId)) recipeCache.Add(cookbook.RecipeId, cookbook.Recipe); } } return cookbook; } public override List GetAllCookbooks() { var cookbooks = base.GetAllCookbooks(); // Add the cookbooks to the cache lock (cookbookCache) { foreach (var cookbook in cookbooks) { // We double check before commiting the recipe. // We could be in this lock, and queued up behind us is an attempt to add a result for the same source:target pair. if (cookbookCache.TryGetValue(cookbook.SourceWCID, out var sourceRecipes)) { if (!sourceRecipes.ContainsKey(cookbook.TargetWCID)) sourceRecipes.Add(cookbook.TargetWCID, cookbook); } else cookbookCache.Add(cookbook.SourceWCID, new Dictionary() { { cookbook.TargetWCID, cookbook } }); } } // build secondary index for RecipeManager_New caching lock (recipeCache) { foreach (var cookbook in cookbooks) { if (!recipeCache.ContainsKey(cookbook.RecipeId)) recipeCache.Add(cookbook.RecipeId, cookbook.Recipe); } } return cookbooks; } public void CacheAllCookbooks() { GetAllCookbooks(); } /// /// Returns the number of Cookbooks currently cached. /// public int GetCookbookCacheCount() { lock (cookbookCache) return cookbookCache.Count(r => r.Value != null); } public void ClearCookbookCache() { lock (cookbookCache) cookbookCache.Clear(); lock (recipeCache) recipeCache.Clear(); } public CookBook GetCachedCookbook(uint sourceWeenieClassId, uint targetWeenieClassId) { lock (cookbookCache) { if (cookbookCache.TryGetValue(sourceWeenieClassId, out var recipesForSource)) { if (recipesForSource.TryGetValue(targetWeenieClassId, out var value)) return value; } } return GetCookbook(sourceWeenieClassId, targetWeenieClassId); // This will add the result into the cache } public Recipe GetCachedRecipe(uint recipeId) { lock (recipeCache) { if (recipeCache.TryGetValue(recipeId, out var recipe)) return recipe; } return GetRecipe(recipeId); // This will add the result in the cache } public override Recipe GetRecipe(WorldDbContext context, uint recipeId) { var recipe = base.GetRecipe(context, recipeId); lock (recipeCache) { if (!recipeCache.ContainsKey(recipeId)) recipeCache.Add(recipeId, recipe); } return recipe; } // ===================================== // Encounter // ===================================== private readonly ConcurrentDictionary> cachedEncounters = new ConcurrentDictionary>(); /// /// Returns the number of Encounters currently cached. /// public int GetEncounterCacheCount() { return cachedEncounters.Count(r => r.Value != null); } public List GetCachedEncountersByLandblock(ushort landblock) { if (cachedEncounters.TryGetValue(landblock, out var value)) return value; using (var context = new WorldDbContext()) { var results = context.Encounter .AsNoTracking() .Where(r => r.Landblock == landblock) .ToList(); cachedEncounters.TryAdd(landblock, results); return results; } } public bool ClearCachedEncountersByLandblock(ushort landblock) { return cachedEncounters.TryRemove(landblock, out _); } // ===================================== // Event // ===================================== private readonly ConcurrentDictionary cachedEvents = new ConcurrentDictionary(); public override List GetAllEvents(WorldDbContext context) { var events = base.GetAllEvents(context); foreach (var result in events) cachedEvents[result.Name.ToLower()] = result; return events; } /// /// Returns the number of Events currently cached. /// public int GetEventsCacheCount() { return cachedEvents.Count(r => r.Value != null); } public Event GetCachedEvent(string name) { var nameToLower = name.ToLower(); if (cachedEvents.TryGetValue(nameToLower, out var value)) return value; using (var context = new WorldDbContext()) { var result = context.Event .AsNoTracking() .FirstOrDefault(r => r.Name.ToLower() == nameToLower); cachedEvents[nameToLower] = result; return result; } } // ===================================== // HousePortal // ===================================== private readonly ConcurrentDictionary> cachedHousePortals = new ConcurrentDictionary>(); /// /// This takes under ? second to complete. /// public void CacheAllHousePortals() { using (var context = new WorldDbContext()) { var results = context.HousePortal .AsNoTracking() .AsEnumerable() .GroupBy(r => r.HouseId); foreach (var result in results) cachedHousePortals[result.Key] = result.ToList(); } } public List GetCachedHousePortals(uint houseId) { if (cachedHousePortals.TryGetValue(houseId, out var value)) return value; using (var context = new WorldDbContext()) { var results = context.HousePortal .AsNoTracking() .Where(p => p.HouseId == houseId) .ToList(); cachedHousePortals[houseId] = results; return results; } } private readonly ConcurrentDictionary> cachedHousePortalsByLandblock = new ConcurrentDictionary>(); public List GetCachedHousePortalsByLandblock(uint landblockId) { if (cachedHousePortalsByLandblock.TryGetValue(landblockId, out var value)) return value; using (var context = new WorldDbContext()) { var results = context.HousePortal .AsNoTracking() .Where(p => landblockId == p.ObjCellId >> 16) .ToList(); cachedHousePortalsByLandblock[landblockId] = results; return results; } } // ===================================== // LandblockInstance // ===================================== private readonly ConcurrentDictionary> cachedLandblockInstances = new ConcurrentDictionary>(); /// /// Returns the number of LandblockInstances currently cached. /// public int GetLandblockInstancesCacheCount() { return cachedLandblockInstances.Count(r => r.Value != null); } /// /// Clears the cached landblock instances for all landblocks /// public void ClearCachedLandblockInstances() { cachedLandblockInstances.Clear(); } /// /// Clears the cached landblock instances for a specific landblock /// public bool ClearCachedInstancesByLandblock(ushort landblock) { return cachedLandblockInstances.TryRemove(landblock, out _); } public List GetCachedInstancesByLandblock(WorldDbContext context, ushort landblock) { if (cachedLandblockInstances.TryGetValue(landblock, out var value)) return value; var results = context.LandblockInstance .Include(r => r.LandblockInstanceLink) .AsNoTracking() .Where(r => r.Landblock == landblock) .ToList(); cachedLandblockInstances.TryAdd(landblock, results.ToList()); return cachedLandblockInstances[landblock]; } /// /// Returns statics spawn map and their links for the landblock /// public List GetCachedInstancesByLandblock(ushort landblock) { using (var context = new WorldDbContext()) return GetCachedInstancesByLandblock(context, landblock); } private readonly ConcurrentDictionary cachedBasementHouseGuids = new ConcurrentDictionary(); public uint GetCachedBasementHouseGuid(ushort landblock) { if (cachedBasementHouseGuids.TryGetValue(landblock, out var value)) return value; using (var context = new WorldDbContext()) { var result = context.LandblockInstance .AsNoTracking() .Where(r => r.Landblock == landblock && r.WeenieClassId != 11730 /* Exclude House Portal */ && r.WeenieClassId != 278 /* Exclude Door */ && r.WeenieClassId != 568 /* Exclude Door (entry) */ && !r.IsLinkChild) .FirstOrDefault(); if (result == null) return 0; cachedBasementHouseGuids[landblock] = result.Guid; return result.Guid; } } // ===================================== // PointsOfInterest // ===================================== private readonly ConcurrentDictionary cachedPointsOfInterest = new ConcurrentDictionary(); /// /// Retrieves all points of interest from the database and adds/updates the points of interest cache entries with every point of interest retrieved. /// 57 entries cached in 00:00:00.0057937 /// public void CacheAllPointsOfInterest() { using (var context = new WorldDbContext()) { var results = context.PointsOfInterest .AsNoTracking(); foreach (var result in results) cachedPointsOfInterest[result.Name.ToLower()] = result; } } /// /// Returns the number of PointsOfInterest currently cached. /// public int GetPointsOfInterestCacheCount() { return cachedPointsOfInterest.Count(r => r.Value != null); } /// /// Returns the PointsOfInterest cache. /// public ConcurrentDictionary GetPointsOfInterestCache() { return new ConcurrentDictionary(cachedPointsOfInterest); } public PointsOfInterest GetCachedPointOfInterest(string name) { var nameToLower = name.ToLower(); if (cachedPointsOfInterest.TryGetValue(nameToLower, out var value)) return value; using (var context = new WorldDbContext()) { var result = context.PointsOfInterest .AsNoTracking() .FirstOrDefault(r => r.Name.ToLower() == nameToLower); cachedPointsOfInterest[nameToLower] = result; return result; } } // ===================================== // Quest // ===================================== private readonly ConcurrentDictionary cachedQuest = new ConcurrentDictionary(); public bool ClearCachedQuest(string questName) { return cachedQuest.TryRemove(questName, out _); } public Quest GetCachedQuest(string questName) { if (cachedQuest.TryGetValue(questName, out var quest)) return quest; using (var context = new WorldDbContext()) { quest = context.Quest.FirstOrDefault(q => q.Name.Equals(questName)); cachedQuest[questName] = quest; return quest; } } // ===================================== // Recipe // ===================================== // ===================================== // Spell // ===================================== private readonly ConcurrentDictionary spellCache = new ConcurrentDictionary(); /// /// This takes under 1 second to complete. /// public void CacheAllSpells() { using (var context = new WorldDbContext()) { var results = context.Spell .AsNoTracking(); foreach (var result in results) spellCache[result.Id] = result; } } /// /// Returns the number of Spells currently cached. /// public int GetSpellCacheCount() { return spellCache.Count(r => r.Value != null); } public void ClearSpellCache() { spellCache.Clear(); } public Spell GetCachedSpell(uint spellId) { if (spellCache.TryGetValue(spellId, out var spell)) return spell; using (var context = new WorldDbContext()) { var result = context.Spell .AsNoTracking() .FirstOrDefault(r => r.Id == spellId); spellCache[spellId] = result; return result; } } // ===================================== // TreasureDeath // ===================================== private readonly ConcurrentDictionary cachedDeathTreasure = new ConcurrentDictionary(); /// /// This takes under 1 second to complete. /// public void CacheAllTreasuresDeath() { using (var context = new WorldDbContext()) { var results = context.TreasureDeath .AsNoTracking(); foreach (var result in results) cachedDeathTreasure[result.TreasureType] = result; } } /// /// Returns the number of TreasureDeath currently cached. /// public int GetDeathTreasureCacheCount() { return cachedDeathTreasure.Count(r => r.Value != null); } public TreasureDeath GetCachedDeathTreasure(uint dataId) { if (cachedDeathTreasure.TryGetValue(dataId, out var value)) return value; using (var context = new WorldDbContext()) { var result = context.TreasureDeath .AsNoTracking() .FirstOrDefault(r => r.TreasureType == dataId); cachedDeathTreasure[dataId] = result; return result; } } // ===================================== // TreasureMaterial // ===================================== // The Key is the Material Code (derived from PropertyInt.TsysMaterialData) // The Value is a list of all private Dictionary>> cachedTreasureMaterialBase; public void CacheAllTreasureMaterialBase() { using (var context = new WorldDbContext()) { var table = new Dictionary>>(); var results = context.TreasureMaterialBase.Where(i => i.Probability > 0).ToList(); foreach (var result in results) { if (!table.TryGetValue((int)result.MaterialCode, out var materialCode)) { materialCode = new Dictionary>(); table.Add((int)result.MaterialCode, materialCode); } if (!materialCode.TryGetValue((int)result.Tier, out var chances)) { chances = new List(); materialCode.Add((int)result.Tier, chances); } chances.Add(result.Clone()); } TreasureMaterialBase_Normalize(table); cachedTreasureMaterialBase = table; } } private const float NormalizeEpsilon = 0.00001f; private void TreasureMaterialBase_Normalize(Dictionary>> materialBase) { foreach (var kvp in materialBase) { var materialCode = kvp.Key; var tiers = kvp.Value; foreach (var kvp2 in tiers) { var tier = kvp2.Key; var list = kvp2.Value; var totalProbability = list.Sum(i => i.Probability); if (Math.Abs(1.0f - totalProbability) < NormalizeEpsilon) continue; //Console.WriteLine($"TotalProbability {totalProbability} found for TreasureMaterialBase {materialCode} tier {tier}"); var factor = 1.0f / totalProbability; foreach (var item in list) item.Probability *= factor; /*totalProbability = list.Sum(i => i.Probability); Console.WriteLine($"After: {totalProbability}");*/ } } } public List GetCachedTreasureMaterialBase(int materialCode, int tier) { if (cachedTreasureMaterialBase == null) CacheAllTreasureMaterialBase(); if (cachedTreasureMaterialBase.TryGetValue(materialCode, out var tiers) && tiers.TryGetValue(tier, out var treasureMaterialBase)) return treasureMaterialBase; else return null; } private Dictionary>> cachedTreasureMaterialColor; public void CacheAllTreasureMaterialColor() { using (var context = new WorldDbContext()) { var table = new Dictionary>>(); var results = context.TreasureMaterialColor.ToList(); foreach (var result in results) { if (!table.TryGetValue((int)result.MaterialId, out var colorCodes)) { colorCodes = new Dictionary>(); table.Add((int)result.MaterialId, colorCodes); } if (!colorCodes.TryGetValue((int)result.ColorCode, out var list)) { list = new List(); colorCodes.Add((int)result.ColorCode, list); } list.Add(result.Clone()); } TreasureMaterialColor_Normalize(table); cachedTreasureMaterialColor = table; } } private void TreasureMaterialColor_Normalize(Dictionary>> materialColor) { foreach (var kvp in materialColor) { var material = kvp.Key; var colorCodes = kvp.Value; foreach (var kvp2 in colorCodes) { var colorCode = kvp2.Key; var list = kvp2.Value; var totalProbability = list.Sum(i => i.Probability); if (Math.Abs(1.0f - totalProbability) < NormalizeEpsilon) continue; //Console.WriteLine($"TotalProbability {totalProbability} found for TreasureMaterialColor {(MaterialType)material} ColorCode {colorCode}"); var factor = 1.0f / totalProbability; foreach (var item in list) item.Probability *= factor; /*totalProbability = list.Sum(i => i.Probability); Console.WriteLine($"After: {totalProbability}");*/ } } } public List GetCachedTreasureMaterialColors(int materialId, int tsysColorCode) { if (cachedTreasureMaterialColor == null) CacheAllTreasureMaterialColor(); if (cachedTreasureMaterialColor.TryGetValue(materialId, out var colorCodes) && colorCodes.TryGetValue(tsysColorCode, out var result)) return result; else return null; } // The Key is the Material Group (technically a MaterialId, but more generic...e.g. "Material.Metal", "Material.Cloth", etc.) // The Value is a list of all private Dictionary>> cachedTreasureMaterialGroups; public void CacheAllTreasureMaterialGroups() { using (var context = new WorldDbContext()) { var table = new Dictionary>>(); var results = context.TreasureMaterialGroups.ToList(); foreach (var result in results) { if (!table.TryGetValue((int)result.MaterialGroup, out var tiers)) { tiers = new Dictionary>(); table.Add((int)result.MaterialGroup, tiers); } if (!tiers.TryGetValue((int)result.Tier, out var list)) { list = new List(); tiers.Add((int)result.Tier, list); } list.Add(result.Clone()); } TreasureMaterialGroups_Normalize(table); cachedTreasureMaterialGroups = table; } } private void TreasureMaterialGroups_Normalize(Dictionary>> materialGroups) { foreach (var kvp in materialGroups) { var materialGroup = kvp.Key; var tiers = kvp.Value; foreach (var kvp2 in tiers) { var tier = kvp2.Key; var list = kvp2.Value; var totalProbability = list.Sum(i => i.Probability); if (Math.Abs(1.0f - totalProbability) < NormalizeEpsilon) continue; //Console.WriteLine($"TotalProbability {totalProbability} found for TreasureMaterialGroup {(MaterialType)materialGroup} tier {tier}"); var factor = 1.0f / totalProbability; foreach (var item in list) item.Probability *= factor; /*totalProbability = list.Sum(i => i.Probability); Console.WriteLine($"After: {totalProbability}");*/ } } } public List GetCachedTreasureMaterialGroup(int materialGroup, int tier) { if (cachedTreasureMaterialGroups == null) CacheAllTreasureMaterialGroups(); if (cachedTreasureMaterialGroups.TryGetValue(materialGroup, out var tiers) && tiers.TryGetValue(tier, out var treasureMaterialGroup)) return treasureMaterialGroup; else return null; } // ===================================== // TreasureWielded // ===================================== private readonly ConcurrentDictionary> cachedWieldedTreasure = new ConcurrentDictionary>(); /// /// This takes under 1 second to complete. /// public void CacheAllTreasureWielded() { using (var context = new WorldDbContext()) { var results = context.TreasureWielded .AsNoTracking() .AsEnumerable() .GroupBy(r => r.TreasureType); foreach (var result in results) cachedWieldedTreasure[result.Key] = result.ToList(); } } /// /// Returns the number of TreasureWielded currently cached. /// public int GetWieldedTreasureCacheCount() { return cachedWieldedTreasure.Count(r => r.Value != null); } public List GetCachedWieldedTreasure(uint dataId) { if (cachedWieldedTreasure.TryGetValue(dataId, out var value)) return value; using (var context = new WorldDbContext()) { var results = context.TreasureWielded .AsNoTracking() .Where(r => r.TreasureType == dataId) .ToList(); cachedWieldedTreasure[dataId] = results; return results; } } public void ClearWieldedTreasureCache() { cachedWieldedTreasure.Clear(); } } }