using Microsoft.Extensions.Caching.Memory; using Npgsql; namespace OpenCand.Repository { public abstract class BaseRepository { protected string ConnectionString { get; private set; } protected NpgsqlConnection? Connection { get; private set; } protected readonly IMemoryCache? _cache; // Default cache settings protected static readonly TimeSpan DefaultCacheExpiration = TimeSpan.MaxValue; protected static readonly CacheItemPriority DefaultCachePriority = CacheItemPriority.Normal; public BaseRepository(IConfiguration configuration, IMemoryCache? cache = null) { ConnectionString = configuration["DatabaseSettings:ConnectionString"] ?? throw new ArgumentNullException("Connection string not found in configuration"); _cache = cache; } /// /// Generic method to get data from cache or execute a factory function if not cached /// /// Type of data to cache /// Unique cache key /// Function to execute if data is not in cache /// Cache expiration time (optional, uses default if not provided) /// Cache priority (optional, uses default if not provided) /// Cached or freshly retrieved data protected async Task GetOrSetCacheAsync( string cacheKey, Func> factory, TimeSpan? expiration = null, CacheItemPriority? priority = null) where T : class { // If caching is not available, execute factory directly if (_cache == null) { return await factory(); } // Try to get cached data first if (_cache.TryGetValue(cacheKey, out T? cachedData) && cachedData != null) { return cachedData; } // If not in cache, execute factory function var result = await factory(); // Only cache non-null results if (result != null) { _cache.Set(cacheKey, result, new MemoryCacheEntryOptions { SlidingExpiration = expiration ?? DefaultCacheExpiration, Priority = priority ?? DefaultCachePriority }); } return result; } /// /// Generic method to get data from cache or execute a synchronous factory function if not cached /// /// Type of data to cache /// Unique cache key /// Function to execute if data is not in cache /// Cache expiration time (optional, uses default if not provided) /// Cache priority (optional, uses default if not provided) /// Cached or freshly retrieved data protected T? GetOrSetCache( string cacheKey, Func factory, TimeSpan? expiration = null, CacheItemPriority? priority = null) where T : class { // If caching is not available, execute factory directly if (_cache == null) { return factory(); } // Try to get cached data first if (_cache.TryGetValue(cacheKey, out T? cachedData) && cachedData != null) { return cachedData; } // If not in cache, execute factory function var result = factory(); // Only cache non-null results if (result != null) { _cache.Set(cacheKey, result, new MemoryCacheEntryOptions { SlidingExpiration = expiration ?? DefaultCacheExpiration, Priority = priority ?? DefaultCachePriority }); } return result; } /// /// Removes an item from cache by key /// /// Cache key to remove protected void ClearCache(string cacheKey) { _cache?.Remove(cacheKey); } /// /// Checks if an item exists in cache /// /// Cache key to check /// True if item exists in cache, false otherwise protected bool IsCached(string cacheKey) { return _cache?.TryGetValue(cacheKey, out _) ?? false; } /// /// Generates a standardized cache key for entity-based caching /// /// Name of the entity (e.g., "Candidato", "Stats") /// Unique identifier for the entity (optional) /// Formatted cache key protected static string GenerateCacheKey(string entityName, object? identifier = null) { return identifier != null ? $"{entityName}_{identifier}" : entityName; } } }