45cb6b9f59
- aggiunta migrations - correzioni versione ef6 da ef8 - correzioni init varie
420 lines
14 KiB
C#
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--> 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 --> 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
|
|
}
|
|
} |