Files
Samuele Locatelli 45cb6b9f59 Fix integrazione preliminare servizi utils.stats x IOC:
- aggiunta migrations
- correzioni versione ef6 da ef8
- correzioni init varie
2026-04-07 10:30:04 +02:00

420 lines
14 KiB
C#

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
{
/// <summary>
/// Classe di partenza x costruzione servizi di accesso dati + cache
/// </summary>
public class BaseServ : IDisposable
{
#region Public Constructors
public BaseServ(IConfiguration configuration, IConnectionMultiplexer redConn)
{
_configuration = configuration;
// Verifica conf trace...
_traceEnabled = _configuration.GetValue<bool>("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<string>("ServerConf:MpIoNS");
}
#endregion Public Constructors
#region Public Methods
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Recupero info IOB x TAB (da info registrate IOB-WIN--&gt; MP-IO)
/// </summary>
/// <param name="IdxMacchina"></param>
/// <returns></returns>
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<IOB_data>($"{rawData}");
source = "REDIS";
}
else
{
Log.Error($"Errore: non trovato valore <IOB_data> 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 <IOB_data> | IdxMacchina: {IdxMacchina}");
}
sw.Stop();
Log.Debug($"IobInfo per {IdxMacchina} | {source} | {sw.Elapsed.TotalMilliseconds}ms");
return result;
}
/// <summary>
/// Recupero info Machine-IOB x TAB (da info registrate IOB-WIN --&gt; MP-IO)
/// </summary>
/// <param name="IdxMacchina"></param>
/// <returns></returns>
public Dictionary<string, string> MachIobConf(string IdxMacchina)
{
string source = "NA";
Stopwatch sw = new Stopwatch();
sw.Start();
Dictionary<string, string> result = new Dictionary<string, string>();
// 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<string, string>();
Log.Debug($"Init valore default MachIobConf | IdxMacchina: {IdxMacchina}");
}
sw.Stop();
Log.Debug($"MachIobConf per {IdxMacchina} | {source} | {sw.Elapsed.TotalMilliseconds}ms");
return result;
}
/// <summary>
/// Recupero Conf Yaml Machine-IOB
/// </summary>
/// <param name="IdxMacchina"></param>
/// <returns></returns>
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
/// <summary>
/// Oggetto per collezione dati Activity (span in Uptrace)
/// </summary>
protected static readonly ActivitySource ActivitySource = new ActivitySource("MP.IOC");
protected static IConfiguration _configuration = null!;
/// <summary>
/// Abilitazione operazioni tracing generiche
/// </summary>
protected readonly bool _traceEnabled = false;
/// <summary>
/// Oggetto per connessione a REDIS
/// </summary>
protected IConnectionMultiplexer _redisConn = null!;
//ISubscriber sub = _redisConn.GetSubscriber();
/// <summary>
/// Oggetto DB redis da impiegare x chiamate R/W
/// </summary>
protected IDatabase _redisDb = null!;
protected JsonSerializerSettings? JSSettings;
protected string MpIoNS = "";
#endregion Protected Fields
#region Protected Properties
/// <summary>
/// Durata cache breve (1 min circa + perturbazione percentuale +/-10%)
/// </summary>
protected TimeSpan FastCache
{
get => TimeSpan.FromSeconds(cacheTtlShort * rnd.Next(900, 1100) / 1000);
}
/// <summary>
/// Durata cache lunga (+ perturbazione percentuale +/-10%)
/// </summary>
protected TimeSpan LongCache
{
get => TimeSpan.FromSeconds(cacheTtlLong * rnd.Next(900, 1100) / 1000);
}
/// <summary>
/// Durata cache MOLTO breve (10 sec circa + perturbazione percentuale +/-10%)
/// </summary>
protected TimeSpan UltraFastCache
{
get => TimeSpan.FromSeconds(cacheTtlShort / 6 * rnd.Next(900, 1100) / 1000);
}
/// <summary>
/// Durata cache MOLTO lunga (+ perturbazione percentuale +/-10%)
/// </summary>
protected TimeSpan UltraLongCache
{
get => TimeSpan.FromSeconds(cacheTtlLong * 10 * rnd.Next(900, 1100) / 1000);
}
#endregion Protected Properties
#region Protected Methods
/// <summary>
/// Helper avvio attività per la funzione tracciata
/// </summary>
/// <param name="methodName"></param>
/// <returns></returns>
protected static Activity? StartActivity([CallerMemberName] string? methodName = null)
{
var activity = ActivitySource.StartActivity(methodName ?? "UNDEF");
activity?.SetTag("host.name", Environment.MachineName);
return activity;
}
/// <summary>
/// Invalida una o più chiavi/pattern in Redis
/// </summary>
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;
}
}
/// <summary>
/// Metodo di flush dati cache Redis
/// </summary>
/// <param name="pattern"></param>
/// <returns></returns>
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);
}
}
}
/// <summary>
/// Helper generale di lettura da cache o da funzione (DB) con caching successivo
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <param name="factory"></param>
/// <param name="expiration"></param>
/// <returns></returns>
protected async Task<T> GetOrSetCacheAsync<T>(string key, Func<Task<T>> 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<T>(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!;
}
/// <summary>
/// Helper trace messaggio log (SE abilitato)
/// </summary>
/// <param name="traceMsg"></param>
/// <param name="reqLevel"></param>
/// <param name="methodName"></param>
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;
}
/// <summary>
/// Helper generale per la telemetria e gestione eccezioni
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="name"></param>
/// <param name="body"></param>
/// <param name="parameters"></param>
/// <returns></returns>
protected async Task<T> TraceAsync<T>(string name, Func<Activity?, Task<T>> 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;
/// <summary>
/// Durata cache lunga IN SECONDI
/// </summary>
private int cacheTtlLong = 60 * 5;
/// <summary>
/// Durata cache breve IN SECONDI
/// </summary>
private int cacheTtlShort = 60 * 1;
private Random rnd = new Random();
/// <summary>
/// Path base chiavi REDIS
/// </summary>
protected readonly string _redisBaseKey = "MP:IOC";
#endregion Private Fields
}
}