using Microsoft.Extensions.Configuration; using MP.Core.Objects; using Newtonsoft.Json; using NLog; using StackExchange.Redis; using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Runtime.CompilerServices; using System.Threading.Tasks; namespace MP.Data.Services { /// /// Classe di partenza x costruzione servizi di accesso dati + cache /// public class BaseServ : IDisposable { #region Public Constructors public BaseServ(IConfiguration configuration, IConnectionMultiplexer redConn) { _configuration = configuration; // Verifica conf trace... _traceEnabled = _configuration.GetValue("Otel:EnableTracing", false); // setup componenti REDIS _redisConn = redConn; _redisDb = _redisConn.GetDatabase(); // json serializer... FIX errore loop circolare https://www.ryadel.com/en/jsonserializationexception-self-referencing-loop-detected-error-fix-entity-framework-asp-net-core/ JSSettings = new JsonSerializerSettings() { ReferenceLoopHandling = ReferenceLoopHandling.Ignore }; // recupero conf speciali MpIoNS = _configuration.GetValue("ServerConf:MpIoNS"); } #endregion Public Constructors #region Public Methods public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// Recupero info IOB x TAB (da info registrate IOB-WIN--> MP-IO) /// /// /// public IOB_data IobInfo(string IdxMacchina) { string source = "DB"; Stopwatch sw = new Stopwatch(); sw.Start(); IOB_data? result = new IOB_data(); // cerco in _redisConn... string currKey = RedHashMpIO($"hM2IOB:{IdxMacchina}"); RedisValue rawData = _redisDb.StringGet(currKey); //if (!string.IsNullOrEmpty($"{rawData}")) if (rawData.HasValue) { result = JsonConvert.DeserializeObject($"{rawData}"); source = "REDIS"; } else { Log.Error($"Errore: non trovato valore valido in REDIS | key: {currKey}"); Log.Info($"REDIS | conf: {_redisConn.Configuration}"); Log.Info($" --> Valore trovato:{Environment.NewLine}{rawData}"); } if (result == null) { result = new IOB_data(); Log.Debug($"Init valore default | IdxMacchina: {IdxMacchina}"); } sw.Stop(); Log.Debug($"IobInfo per {IdxMacchina} | {source} | {sw.Elapsed.TotalMilliseconds}ms"); return result; } /// /// Recupero info Machine-IOB x TAB (da info registrate IOB-WIN --> MP-IO) /// /// /// public Dictionary MachIobConf(string IdxMacchina) { string source = "NA"; Stopwatch sw = new Stopwatch(); sw.Start(); Dictionary result = new Dictionary(); // cerco in _redisConn... string currKey = RedHashMpIO($"IOB:{IdxMacchina}:MachIobConf"); try { result = _redisDb .HashGetAll(currKey) .ToDictionary(x => $"{x.Name}", x => $"{x.Value}"); source = "REDIS"; } catch (Exception exc) { Log.Error($"Errore in MachIobConf{Environment.NewLine}{exc}"); } if (result == null) { result = new Dictionary(); Log.Debug($"Init valore default MachIobConf | IdxMacchina: {IdxMacchina}"); } sw.Stop(); Log.Debug($"MachIobConf per {IdxMacchina} | {source} | {sw.Elapsed.TotalMilliseconds}ms"); return result; } /// /// Recupero Conf Yaml Machine-IOB /// /// /// public string MachIobYamlConf(string IdxMacchina) { string source = "NA"; Stopwatch sw = new Stopwatch(); sw.Start(); string result = ""; // cerco in _redisConn... string currKey = RedHashMpIO($"IOB:{IdxMacchina}:ConfYaml"); try { result = _redisDb.StringGet(currKey); source = "REDIS"; } catch (Exception exc) { Log.Error($"Errore in MachIobYamlConf{Environment.NewLine}{exc}"); } if (result == null) { result = ""; Log.Debug($"Init valore default MachIobYamlConf | IdxMacchina: {IdxMacchina}"); } sw.Stop(); Log.Debug($"MachIobYamlConf per {IdxMacchina} | {source} | {sw.Elapsed.TotalMilliseconds}ms"); return result; } #endregion Public Methods #region Protected Fields /// /// Oggetto per collezione dati Activity (span in Uptrace) /// protected static readonly ActivitySource ActivitySource = new ActivitySource("MP.IOC"); protected static IConfiguration _configuration = null!; /// /// Abilitazione operazioni tracing generiche /// protected readonly bool _traceEnabled = false; /// /// Oggetto per connessione a REDIS /// protected IConnectionMultiplexer _redisConn = null!; //ISubscriber sub = _redisConn.GetSubscriber(); /// /// Oggetto DB redis da impiegare x chiamate R/W /// protected IDatabase _redisDb = null!; protected JsonSerializerSettings? JSSettings; protected string MpIoNS = ""; #endregion Protected Fields #region Protected Properties /// /// Durata cache breve (1 min circa + perturbazione percentuale +/-10%) /// protected TimeSpan FastCache { get => TimeSpan.FromSeconds(cacheTtlShort * rnd.Next(900, 1100) / 1000); } /// /// Durata cache lunga (+ perturbazione percentuale +/-10%) /// protected TimeSpan LongCache { get => TimeSpan.FromSeconds(cacheTtlLong * rnd.Next(900, 1100) / 1000); } /// /// Durata cache MOLTO breve (10 sec circa + perturbazione percentuale +/-10%) /// protected TimeSpan UltraFastCache { get => TimeSpan.FromSeconds(cacheTtlShort / 6 * rnd.Next(900, 1100) / 1000); } /// /// Durata cache MOLTO lunga (+ perturbazione percentuale +/-10%) /// protected TimeSpan UltraLongCache { get => TimeSpan.FromSeconds(cacheTtlLong * 10 * rnd.Next(900, 1100) / 1000); } #endregion Protected Properties #region Protected Methods /// /// Helper avvio attività per la funzione tracciata /// /// /// protected static Activity? StartActivity([CallerMemberName] string? methodName = null) { var activity = ActivitySource.StartActivity(methodName ?? "UNDEF"); activity?.SetTag("host.name", Environment.MachineName); return activity; } /// /// Invalida una o più chiavi/pattern in Redis /// protected async Task ClearCacheAsync(params string[] patterns) { foreach (var pattern in patterns) { // Chiamata al tuo metodo esistente await ExecFlushRedisPatternAsync((RedisValue)pattern); } } protected virtual void Dispose(bool disposing) { if (!_disposed) { if (disposing) { // Free managed resources here _redisDb = null; } // Free unmanaged resources here _disposed = true; } } /// /// Metodo di flush dati cache Redis /// /// /// protected async Task ExecFlushRedisPatternAsync(RedisValue pattern) { // Qui inserisci la tua logica attuale (es. via Lua script o Keys/Scan) // Esempio rapido via server scan: var endpoints = _redisConn.GetEndPoints(); foreach (var endpoint in endpoints) { var server = _redisConn.GetServer(endpoint); await foreach (var key in server.KeysAsync(_redisDb.Database, pattern)) { await _redisDb.KeyDeleteAsync(key); } } } /// /// Helper generale di lettura da cache o da funzione (DB) con caching successivo /// /// /// /// /// /// protected async Task GetOrSetCacheAsync(string key, Func> factory, TimeSpan? expiration = null, [CallerMemberName] string? caller = null) { using var activity = StartActivity(); string source = "DB"; // 1. Provo Redis var cached = await _redisDb.StringGetAsync(key); if (cached.HasValue) { source = "REDIS"; var cachedResult = JsonConvert.DeserializeObject(cached!)!; activity?.SetTag("data.source", source); LogTrace($"{source} | trace: {activity?.TraceId} | {activity?.Duration.TotalMilliseconds}ms", NLog.LogLevel.Trace, caller); return cachedResult; } // 2. Chiamo il factory (DB) T result = await factory(); if (result != null) { // 3. Salva in Redis per la prossima volta var serialized = JsonConvert.SerializeObject(result, JSSettings); await _redisDb.StringSetAsync(key, serialized, expiration ?? LongCache); } // sistemo activity tracking data activity?.SetTag("data.source", source); activity?.Stop(); // log in console LogTrace($"GetOrSetCacheAsync | {source} | trace: {activity?.TraceId} | {activity?.Duration.TotalMilliseconds:N3}ms", NLog.LogLevel.Trace, caller); return result!; } /// /// Helper trace messaggio log (SE abilitato) /// /// /// /// protected void LogTrace(string traceMsg, NLog.LogLevel? reqLevel = null, [CallerMemberName] string? methodName = null) { if (!_traceEnabled) return; reqLevel ??= NLog.LogLevel.Debug; // Loggo! Log.Log(reqLevel, $"{methodName} | {traceMsg}"); } protected string RedHashMpIO(string keyName) { string result = keyName; try { result = $"{MpIoNS}:{keyName}".Replace("\\", "_"); } catch (Exception exc) { Log.Error($"Errore in RedHashMpIO{Environment.NewLine}{exc}"); } return result; } /// /// Helper generale per la telemetria e gestione eccezioni /// /// /// /// /// /// protected async Task TraceAsync(string name, Func> body, object? parameters = null) { using var activity = ActivitySource.StartActivity(name); try { if (parameters != null) { activity?.SetTag("params", JsonConvert.SerializeObject(parameters)); } var result = await body(activity); activity?.SetStatus(ActivityStatusCode.Ok); activity?.Stop(); LogTrace($"TraceAsync | trace: {activity?.TraceId} | {activity?.Duration.TotalMilliseconds}ms", methodName: name); return result; } catch (Exception ex) { activity?.SetStatus(ActivityStatusCode.Error, ex.Message); //Log.Error(ex, "Errore in {MethodName}", name); LogTrace($"Errore in {name}", NLog.LogLevel.Error, name); throw; // Riesponi l'eccezione per il tracking globale } } #endregion Protected Methods #region Private Fields private static Logger Log = LogManager.GetCurrentClassLogger(); private bool _disposed = false; /// /// Durata cache lunga IN SECONDI /// private int cacheTtlLong = 60 * 5; /// /// Durata cache breve IN SECONDI /// private int cacheTtlShort = 60 * 1; private Random rnd = new Random(); /// /// Path base chiavi REDIS /// protected readonly string _redisBaseKey = "MP:IOC"; #endregion Private Fields } }