using Microsoft.Extensions.Configuration; using MP.Data.DbModels; using Newtonsoft.Json; using NLog; using StackExchange.Redis; using System; using System.Collections.Generic; using System.Data; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; namespace MP.Data.Services { /// /// Classe accesso dati filtrati per operatore /// public class ListSelectDataSrv : BaseServ { #region Public Constructors public ListSelectDataSrv(IConfiguration configuration, IConnectionMultiplexer redConn) : base(configuration, redConn) { // conf DB string connStr = _configuration.GetConnectionString("MP.All"); if (string.IsNullOrEmpty(connStr)) { Log.Error("ConnString empty!"); } else { StringBuilder sb = new StringBuilder(); dbController = new Controllers.MpSpecController(configuration); sb.AppendLine($"ListSelectDataSrv | MpSpecController OK"); Log.Info(sb.ToString()); } } #endregion Public Constructors #region Public Properties public static Controllers.MpSpecController dbController { get; set; } = null!; #endregion Public Properties #region Public Methods /// /// Elenco tabella Articoli da filtro /// /// Numero record max da recuperare /// cod azienda, * = tutte /// Ricerca testuale /// public async Task> ArticoliGetSearch(int numRecord, string azienda, string searchVal) { string source = "DB"; Stopwatch sw = new Stopwatch(); sw.Start(); List? result = new List(); // cerco in _redisConn... string currKey = $"{redisBaseKey}:Art:{azienda}:{searchVal}:{numRecord}"; RedisValue rawData = await _redisDb.StringGetAsync(currKey); if (rawData.HasValue) { result = JsonConvert.DeserializeObject>($"{rawData}"); source = "REDIS"; } else { result = dbController.ArticoliGetSearch(numRecord, azienda, searchVal); #if false result = await Task.FromResult(dbController.ArticoliGetSearch(numRecord, azienda, searchVal)); #endif // serializzp e salvo... rawData = JsonConvert.SerializeObject(result); await _redisDb.StringSetAsync(currKey, rawData, LongCache); } if (result == null) { result = new List(); } sw.Stop(); Log.Debug($"ArticoliGetSearch | azienda: {azienda} | searchVal: {searchVal} | numRecord: {numRecord} | {source} | {sw.Elapsed.TotalMilliseconds}ms"); return result; } /// /// Recupero elenco config /// /// public async Task> ConfigGetAll() { string source = "DB"; Stopwatch sw = new Stopwatch(); sw.Start(); List? result = new List(); // cerco in _redisConn... string currKey = $"{redisBaseKey}:Conf"; RedisValue rawData = await _redisDb.StringGetAsync(currKey); if (rawData.HasValue) { result = JsonConvert.DeserializeObject>($"{rawData}"); source = "REDIS"; } else { result = dbController.ConfigGetAll(); // serializzo e salvo... rawData = JsonConvert.SerializeObject(result); _redisDb.StringSet(currKey, rawData, LongCache); } if (result == null) { result = new List(); } sw.Stop(); Log.Debug($"ConfigGetAll | {source} | {sw.Elapsed.TotalMilliseconds}ms"); return result; } /// /// Pulizia cache Redis (tutta) /// /// public async Task FlushCache() { RedisValue pattern = new RedisValue($"{redisBaseKey}:*"); bool answ = await ExecFlushRedisPattern(pattern); return answ; } /// /// Pulizia cache Redis per chiave specifica (da redisBaseKey...) /// /// public async Task FlushCache(string KeyReq) { RedisValue pattern = new RedisValue($"{redisBaseKey}:{KeyReq}:*"); bool answ = await ExecFlushRedisPattern(pattern); return answ; } /// /// Elenco link JQM (totale) /// /// public async Task> ListLinkAll() { string source = "DB"; Stopwatch sw = new Stopwatch(); sw.Start(); List? result = new List(); // cerco in _redisConn... string currKey = $"{redisBaseKey}:Menu:ALL"; RedisValue rawData = await _redisDb.StringGetAsync(currKey); if (rawData.HasValue) { result = JsonConvert.DeserializeObject>($"{rawData}"); source = "REDIS"; } else { result = await Task.FromResult(dbController.ListLinkAll()); // serializzp e salvo... rawData = JsonConvert.SerializeObject(result); await _redisDb.StringSetAsync(currKey, rawData, UltraLongCache); } if (result == null) { result = new List(); } sw.Stop(); Log.Debug($"ListLinkAll | tipoLink: * | {source} | {sw.Elapsed.TotalMilliseconds}ms"); return result; } /// /// Elenco link JQM dato filtro tipo /// /// /// public async Task> ListLinkFilt(string tipoLink) { string source = "DB"; Stopwatch sw = new Stopwatch(); sw.Start(); List? result = new List(); // cerco in _redisConn... string currKey = $"{redisBaseKey}:Menu:{tipoLink}"; RedisValue rawData = await _redisDb.StringGetAsync(currKey); //if (!string.IsNullOrEmpty($"{rawData}")) if (rawData.HasValue) { result = JsonConvert.DeserializeObject>($"{rawData}"); source = "REDIS"; } else { result = await Task.FromResult(dbController.ListLinkFilt(tipoLink)); // serializzp e salvo... rawData = JsonConvert.SerializeObject(result); await _redisDb.StringSetAsync(currKey, rawData, UltraLongCache); } if (result == null) { result = new List(); } sw.Stop(); Log.Debug($"ListLinkFilt | tipoLink: {tipoLink} | {source} | {sw.Elapsed.TotalMilliseconds}ms"); return result; } /// /// Elenco Macchine dato operatore secondo gruppi (macchine/operatore) /// /// /// public async Task> MacchineByMatrOper(int MatrOpr) { string source = "DB"; Stopwatch sw = new Stopwatch(); sw.Start(); List? result = new List(); // cerco in _redisConn... string currKey = $"{redisBaseKey}:Macc:{MatrOpr}"; RedisValue rawData = await _redisDb.StringGetAsync(currKey); //if (!string.IsNullOrEmpty($"{rawData}")) if (rawData.HasValue) { result = JsonConvert.DeserializeObject>($"{rawData}"); source = "REDIS"; } else { result = await Task.FromResult(dbController.MacchineByMatrOper(MatrOpr)); // serializzp e salvo... rawData = JsonConvert.SerializeObject(result); await _redisDb.StringSetAsync(currKey, rawData, UltraLongCache); } if (result == null) { result = new List(); } sw.Stop(); Log.Debug($"MacchineGetFilt | MatrOpr: {MatrOpr} | {source} | {sw.Elapsed.TotalMilliseconds}ms"); return result; } #endregion Public Methods #region Protected Methods protected override void Dispose(bool disposing) { if (!_disposed) { if (disposing) { // Free managed resources here dbController.Dispose(); } // Free unmanaged resources here _disposed = true; } // Call base class implementation. base.Dispose(disposing); } #endregion Protected Methods #region Private Fields private static Logger Log = LogManager.GetCurrentClassLogger(); private bool _disposed = false; private string redisBaseKey = "MP:ALL:Cache"; #endregion Private Fields #region Private Methods /// /// Esegue flush memoria _redisConn dato pattern /// /// /// private async Task ExecFlushRedisPattern(RedisValue pattern) { Log.Debug($"Richiesta flush pattern: {pattern}"); // 1. Target ONLY master (le replica sono in read-only) var master = _redisConn.GetEndPoints() .Where(ep => _redisConn.GetServer(ep).IsConnected && !_redisConn.GetServer(ep).IsReplica) .FirstOrDefault(); if (master == null) { Log.Warn($"Nessun master Redis raggiungibile per il pattern {pattern}"); return false; } // 2. Flush intero DB se richiesto if (pattern.ToString() == "*") { Log.Debug($"Full DB reset da pattern {pattern}"); if (master != null) { _redisConn.GetServer(master).FlushDatabase(_redisDb.Database); Log.Info($"Flush database {_redisDb.Database} completato"); } return true; } // altrimenti faccio ciclo! var server = _redisConn.GetServer(master); var db = _redisConn.GetDatabase(_redisDb.Database); const int batchSize = 500; var batch = new List(batchSize); int deletedCount = 0; try { // KeysAsync usa SCAN automaticamente quando i risultati sono grandi await foreach (var key in server.KeysAsync( database: _redisDb.Database, pattern: pattern.ToString(), pageSize: batchSize)) { batch.Add(key); if (batch.Count >= batchSize) { // Esecuzione batch in parallelo controllato await Task.WhenAll(batch.Select(k => db.KeyDeleteAsync(k))); batch.Clear(); deletedCount += batchSize; } } // Restanti if (batch.Count > 0) { await Task.WhenAll(batch.Select(k => db.KeyDeleteAsync(k))); deletedCount += batch.Count; } Log.Info("Flush pattern {Pattern}: eliminate {Count} chiavi", pattern, deletedCount); return true; } catch (RedisConnectionException ex) { Log.Error(ex, "Connessione Redis persa durante il flush di {pattern}", pattern); return false; } catch (Exception ex) { Log.Error(ex, "Errore imprevisto nel flush pattern {pattern}", pattern); throw; } #if false bool answ = false; var masterEndpoint = _redisConn.GetEndPoints() .Where(ep => _redisConn.GetServer(ep).IsConnected && !_redisConn.GetServer(ep).IsReplica) .FirstOrDefault(); // se pattern è "*" elimino intero DB... if (masterEndpoint != null && (pat2Flush.Equals(new RedisValue("*")) || pat2Flush == RedisValue.Null)) { _redisConn.GetServer(masterEndpoint).FlushDatabase(database: _redisDb.Database); } else { var server = _redisConn.GetServer(masterEndpoint); var keys = server.Keys(database: _redisDb.Database, pattern: pat2Flush, pageSize: 1000); var deleteTasks = new List(); foreach (var key in keys) { deleteTasks.Add(_redisDb.KeyDeleteAsync(key)); if (deleteTasks.Count >= 1000) { await Task.WhenAll(deleteTasks); deleteTasks.Clear(); } } if (deleteTasks.Count > 0) { await Task.WhenAll(deleteTasks); } answ = true; } return answ; #endif } #endregion Private Methods } }