using Microsoft.Extensions.Caching.Memory;
using Npgsql;
using System.Text.Json;
using System.Text;
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,
Size = EstimateSize(result)
});
}
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,
Size = EstimateSize(result)
});
}
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;
}
///
/// Estimates the memory size of an object by serializing it to JSON
///
/// Type of the object
/// The object to estimate size for
/// Estimated size in bytes
private static long EstimateSize(T obj)
{
if (obj == null) return 0;
try
{
var json = JsonSerializer.Serialize(obj);
return Encoding.UTF8.GetByteCount(json);
}
catch
{
return 1024; // Default estimate if serialization fails
}
}
}
}