Class QueryCache
Facade over ICacheStore providing query-result caching with two key properties:
- Time-quantized keys — DateTime parameters derived from
UtcNoware rounded to a configurable window before hashing, so.Where(x => x.At >= UtcNow.AddDays(-30))no longer produces a unique key per call. - Table-version invalidation — mutations bump a per-table version counter that participates in the cache key; prior entries simply become un-hittable and are swept on eviction. No need to track-and-evict individual keys.
Keeps a static facade so existing callers (QueryBuilder, Repository)
compile unchanged.
public static class QueryCache
- Inheritance
-
QueryCache
- Inherited Members
Properties
Count
Total cached entries (best-effort).
public static int Count { get; }
Property Value
Methods
Clear()
Clear the entire cache (admin / tests).
public static void Clear()
Configure(bool, int, int, int, bool)
Apply runtime configuration from CacheConfiguration.
public static void Configure(bool enabled, int maxEntries, int timeQuantizeSeconds, int defaultTtlSeconds = 60, bool publishEvents = true)
Parameters
GetOrSetAsync<T>(string, string, Func<Task<T>>, TimeSpan, string?)
Cache-aside helper. Returns the cached value if fresh; otherwise runs factory,
stores the result, and returns it.
Failure CodeLogic.Core.Results.Result<T> values are NEVER cached and never served as cache hits — a transient DB failure during a cold-warmup cannot poison the cache. If a previously-cached value is detected as a failure (legacy entries from older versions), it's evicted on read so the call falls through to a fresh execution.
public static Task<T> GetOrSetAsync<T>(string cacheKey, string tableName, Func<Task<T>> factory, TimeSpan ttl, string? connectionId = null)
Parameters
Returns
- Task<T>
Type Parameters
T
GetStats()
Diagnostic snapshot: total entries + per-table counts + table-version map. Returned types are immutable copies; safe to log or render on an admin page without holding cache locks.
public static QueryCacheStats GetStats()
Returns
Invalidate(string)
Invalidate all cached entries for the given table.
Two-step: bump the per-table version (so future reads compute a different cache key and miss any in-flight refresh writes), then evict the now-orphaned entries from the underlying store so they don't accumulate. Without the second step, every mutation leaves behind the previous version's entries until TTL/LRU clears them, which on a busy app produces unbounded memory growth.
When a table has active SmartCachePool entries, the
pool's background refresh is the source of truth for freshness — it
re-executes queries every RefreshEvery and writes to the
current version's cache key. Flushing the cache on every mutation
(e.g. a stats ingest INSERT) would defeat the pool: every request
between the flush and the next refresh tick hits the DB cold. For
pool-managed tables we skip the eviction and let the pool refresh
deliver naturally-stale-within-interval reads instead.
public static void Invalidate(string tableName)
Parameters
tableNamestring
Invalidate<T>()
Invalidate for the table behind entity type T.
public static void Invalidate<T>() where T : class
Type Parameters
T
SetConnectionOverride(string, bool?)
Registers (or clears, when enabled is null) a per-database override
of the global CL.MySQL2.Services.QueryCache.Enabled switch. Called by MySQL2Library at init.
public static void SetConnectionOverride(string connectionId, bool? enabled)
Parameters
SetDirectAsync(string, object, TimeSpan, string, CancellationToken)
Direct write into the cache, bypassing the cache-aside flow. Used by SmartCachePool to overwrite an entry after a background refresh produces a fresh value. No observability event is emitted — the refresh isn't a cache hit or miss from a caller's perspective.
public static Task SetDirectAsync(string cacheKey, object value, TimeSpan ttl, string tableName, CancellationToken ct = default)
Parameters
cacheKeystringvalueobjectttlTimeSpantableNamestringctCancellationToken
Returns
UseCoordinator(ICacheCoordinator)
Installs a multi-node ICacheCoordinator (e.g. a Redis pub/sub adapter) so mutations fan out to peers and smart-cache pools refresh single-flight. Wires the coordinator's peer-invalidation callback to the local (non-broadcasting) invalidation path. Pair with a shared ICacheStore via UseStore(ICacheStore).
public static void UseCoordinator(ICacheCoordinator coordinator)
Parameters
coordinatorICacheCoordinator
UseStore(ICacheStore)
Replace the underlying store (e.g. with a Redis adapter).
public static void UseStore(ICacheStore store)
Parameters
storeICacheStore