using GPW.CORE.Data.DbModels; using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.Caching.Memory; using Newtonsoft.Json; using NLog; using System.Diagnostics; using System.Text; namespace GPW.CORE.UI.Data { public class GpwDataService : IDisposable { #region Private Fields private static IConfiguration _configuration = null!; private static ILogger _logger = null!; private static JsonSerializerSettings? JSSettings; private static NLog.Logger Log = LogManager.GetCurrentClassLogger(); //private readonly IEmailSender _emailSender; //private readonly UserManager _userManager; private readonly IDistributedCache distributedCache; private readonly IMemoryCache memoryCache; private List cachedDataList = new List(); /// /// Durata assoluta massima della cache IN SECONDI /// private int chAbsExp = 60 * 5; /// /// Durata della cache IN SECONDI in modalità inattiva (non acceduta) prima di venire rimossa /// NON estende oltre il tempo massimo di validità della cache (chAbsExp) /// private int chSliExp = 60 * 1; #endregion Private Fields #region Protected Fields protected const string rKeyAKV = "Cache:AKV"; protected const string rKeyCalcOreFase = "Cache:CalcOreFase"; protected const string rKeyCalcOreProj = "Cache:CalcOreProj"; protected const string rKeyCliAll = "Cache:CliAll"; protected const string rKeyDailyData = "Cache:DailyData"; protected const string rKeyDipendenti = "Cache:Dipendenti"; protected const string rKeyFasiAll = "Cache:FasiAll"; protected const string rKeyGrpAll = "Cache:GrpAll"; protected const string rKeyParetoRegAtt = "Cache:ParetoRegAtt"; protected const string rKeyProjAll = "Cache:ProjAll"; protected const string rKeyRilTemp = "Cache:RilTemp"; protected const string rKeyVC19 = "Cache:VC19"; protected const string rKeyVetoIns = "Cache:VetoInsert"; protected const string rKeyWeekStats = "Cache:WeekStats"; protected static string connStringBBM = ""; #endregion Protected Fields #region Public Fields public static CORE.Data.Controllers.GPWController dbController = null!; public bool VetoInsert = false; #endregion Public Fields #region Public Constructors public GpwDataService(IConfiguration configuration, ILogger logger, IMemoryCache memoryCache, IDistributedCache distributedCache) { _logger = logger; _configuration = configuration; // conf cache this.memoryCache = memoryCache; this.distributedCache = distributedCache; // 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 }; // cod app CodApp = _configuration["CodApp"]; var strVeto = _configuration["VetoIns"]; _ = bool.TryParse(strVeto, out VetoInsert); // conf DB string connStr = _configuration.GetConnectionString("GPW.DB"); if (string.IsNullOrEmpty(connStr)) { _logger.LogError("ConnString empty!"); } else { dbController = new CORE.Data.Controllers.GPWController(configuration); } } #endregion Public Constructors #region Public Properties public string CodApp { get; set; } = ""; #endregion Public Properties #region Private Methods private DistributedCacheEntryOptions cacheOpt(bool fastCache) { var numSecAbsExp = fastCache ? chAbsExp : chAbsExp * 10; var numSecSliExp = fastCache ? chSliExp : chSliExp * 10; return new DistributedCacheEntryOptions().SetAbsoluteExpiration(DateTime.Now.AddSeconds(numSecAbsExp)).SetSlidingExpiration(TimeSpan.FromSeconds(numSecSliExp)); } private DistributedCacheEntryOptions cacheOpt(int multFact) { var numSecAbsExp = chAbsExp * multFact; var numSecSliExp = chSliExp * multFact; return new DistributedCacheEntryOptions().SetAbsoluteExpiration(DateTime.Now.AddSeconds(numSecAbsExp)).SetSlidingExpiration(TimeSpan.FromSeconds(numSecSliExp)); } #endregion Private Methods #region Protected Methods /// /// Registra in cache chiave se non fosse già in elenco /// /// protected void trackCache(string newKey) { if (!cachedDataList.Contains(newKey)) { cachedDataList.Add(newKey); } } #endregion Protected Methods #region Public Methods /// /// Elenco AKV /// public async Task> AKVList() { List dbResult = new List(); // cerco da cache string currKey = rKeyAKV; trackCache(currKey); string rawData; var redisDataList = await distributedCache.GetAsync(currKey); if (redisDataList != null) { rawData = Encoding.UTF8.GetString(redisDataList); dbResult = JsonConvert.DeserializeObject>(rawData); } else { Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); dbResult = dbController.AnagKeyValGetAll(); rawData = JsonConvert.SerializeObject(dbResult, JSSettings); redisDataList = Encoding.UTF8.GetBytes(rawData); await distributedCache.SetAsync(currKey, redisDataList, cacheOpt(500)); stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; Log.Trace($"Effettuata lettura da DB + caching per AKVList: {ts.TotalMilliseconds} ms"); } if (dbResult == null) { dbResult = new List(); } return await Task.FromResult(dbResult); } /// /// Recupera l'elenco fasi (tutte) /// /// public async Task> AnagClientiAll() { List? dbResult = new List(); string currKey = $"{rKeyCliAll}"; string rawData; var redisDataList = await distributedCache.GetAsync(currKey); if (redisDataList != null) { rawData = Encoding.UTF8.GetString(redisDataList); dbResult = JsonConvert.DeserializeObject>(rawData); } else { Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); dbResult = dbController.AnagClientiAll(); rawData = JsonConvert.SerializeObject(dbResult, JSSettings); redisDataList = Encoding.UTF8.GetBytes(rawData); await distributedCache.SetAsync(currKey, redisDataList, cacheOpt(true)); stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; Log.Trace($"Effettuata lettura da DB + caching per AnagClientiAll: {ts.TotalMilliseconds} ms"); } if (dbResult == null) { dbResult = new List(); } return await Task.FromResult(dbResult); } /// /// Recupera l'elenco fasi (tutte) /// /// public async Task> AnagFasiAll() { List? dbResult = new List(); string currKey = $"{rKeyFasiAll}"; string rawData; var redisDataList = await distributedCache.GetAsync(currKey); if (redisDataList != null) { rawData = Encoding.UTF8.GetString(redisDataList); dbResult = JsonConvert.DeserializeObject>(rawData); } else { Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); dbResult = dbController.AnagFasiAll(false); rawData = JsonConvert.SerializeObject(dbResult, JSSettings); redisDataList = Encoding.UTF8.GetBytes(rawData); await distributedCache.SetAsync(currKey, redisDataList, cacheOpt(true)); stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; Log.Trace($"Effettuata lettura da DB + caching per AnagFasiAll: {ts.TotalMilliseconds} ms"); } if (dbResult == null) { dbResult = new List(); } return await Task.FromResult(dbResult); } /// /// Recupera info x una specifica FASE /// /// public async Task AnagFasiSearch(int idxFase) { AnagFasiModel dbResult = new AnagFasiModel(); var rawData = await AnagFasiAll(); if (rawData != null) { dbResult = rawData.FirstOrDefault(x => x.IdxFase == idxFase); } return await Task.FromResult(dbResult); } /// /// Recupera l'elenco gruppi (tutti) /// /// public async Task> AnagGruppiAll() { List? dbResult = new List(); string currKey = $"{rKeyGrpAll}"; string rawData; var redisDataList = await distributedCache.GetAsync(currKey); if (redisDataList != null) { rawData = Encoding.UTF8.GetString(redisDataList); dbResult = JsonConvert.DeserializeObject>(rawData); } else { Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); dbResult = dbController.AnagGruppiAll(); rawData = JsonConvert.SerializeObject(dbResult, JSSettings); redisDataList = Encoding.UTF8.GetBytes(rawData); await distributedCache.SetAsync(currKey, redisDataList, cacheOpt(true)); stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; Log.Trace($"Effettuata lettura da DB + caching per AnagGruppiAll: {ts.TotalMilliseconds} ms"); } if (dbResult == null) { dbResult = new List(); } return await Task.FromResult(dbResult); } /// /// Recupera l'elenco progetti (tutti) /// /// public async Task> AnagProjAll() { List? dbResult = new List(); string currKey = $"{rKeyProjAll}"; string rawData; var redisDataList = await distributedCache.GetAsync(currKey); if (redisDataList != null) { rawData = Encoding.UTF8.GetString(redisDataList); dbResult = JsonConvert.DeserializeObject>(rawData); } else { Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); dbResult = dbController.AnagProjAll(false); rawData = JsonConvert.SerializeObject(dbResult, JSSettings); redisDataList = Encoding.UTF8.GetBytes(rawData); await distributedCache.SetAsync(currKey, redisDataList, cacheOpt(true)); stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; Log.Trace($"Effettuata lettura da DB + caching per AnagProjAll: {ts.TotalMilliseconds} ms"); } if (dbResult == null) { dbResult = new List(); } return await Task.FromResult(dbResult); } /// /// Recupera info x uno specifico PROJ /// /// public async Task AnagProjSearch(int idxProj) { AnagProgettiModel dbResult = new AnagProgettiModel(); var rawData = await AnagProjAll(); if (rawData != null) { dbResult = rawData.FirstOrDefault(x => x.IdxProgetto == idxProj); } return await Task.FromResult(dbResult); } /// /// Vista dati per FASE (tipicamente master...) con totalizzaizone ore budget/real e stato fase /// /// /// public async Task CalcOreFase(int idxFase) { CalcOreFasiModel? dbResult = new CalcOreFasiModel(); string currKey = $"{rKeyCalcOreFase}:{idxFase}"; trackCache(currKey); string rawData; var redisDataList = await distributedCache.GetAsync(currKey); if (redisDataList != null) { rawData = Encoding.UTF8.GetString(redisDataList); dbResult = JsonConvert.DeserializeObject(rawData); } else { Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); dbResult = dbController.CalcOreFase(idxFase); rawData = JsonConvert.SerializeObject(dbResult, JSSettings); redisDataList = Encoding.UTF8.GetBytes(rawData); await distributedCache.SetAsync(currKey, redisDataList, cacheOpt(true)); stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; Log.Trace($"Effettuata lettura da DB + caching per CalcOreFase: {ts.TotalMilliseconds} ms"); } if (dbResult == null) { dbResult = new CalcOreFasiModel(); } return await Task.FromResult(dbResult); } /// /// Vista dati per progetto con totalizzaizone ore budget/real /// /// /// public async Task CalcOreProj(int idxProj) { CalcOreProgettiModel? dbResult = new CalcOreProgettiModel(); string currKey = $"{rKeyCalcOreProj}:{idxProj}"; trackCache(currKey); string rawData; var redisDataList = await distributedCache.GetAsync(currKey); if (redisDataList != null) { rawData = Encoding.UTF8.GetString(redisDataList); dbResult = JsonConvert.DeserializeObject(rawData); } else { Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); dbResult = dbController.CalcOreProj(idxProj); rawData = JsonConvert.SerializeObject(dbResult, JSSettings); redisDataList = Encoding.UTF8.GetBytes(rawData); await distributedCache.SetAsync(currKey, redisDataList, cacheOpt(true)); stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; Log.Trace($"Effettuata lettura da DB + caching per CalcOreProj: {ts.TotalMilliseconds} ms"); } if (dbResult == null) { dbResult = new CalcOreProgettiModel(); } return await Task.FromResult(dbResult); } /// /// Recupera l'elenco dei controlli VC19 nel periodo indicato x dipendente /// /// Dipendente interessato /// Data di riferimento (ultima/corrente) /// NUm settimane precedenti da recuperare /// public async Task> CheckVC19List(int idxDipendente, DateTime dtInizio, DateTime dtFine) { List? dbResult = new List(); string currKey = $"{rKeyVC19}:{dtInizio:yyyyMMdd}:{dtFine:yyyMMdd}"; trackCache(currKey); string rawData; var redisDataList = await distributedCache.GetAsync(currKey); if (redisDataList != null) { rawData = Encoding.UTF8.GetString(redisDataList); dbResult = JsonConvert.DeserializeObject>(rawData); } else { Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); dbResult = dbController.CheckVC19List(idxDipendente, dtInizio, dtFine); rawData = JsonConvert.SerializeObject(dbResult, JSSettings); redisDataList = Encoding.UTF8.GetBytes(rawData); await distributedCache.SetAsync(currKey, redisDataList, cacheOpt(true)); stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; Log.Trace($"Effettuata lettura da DB + caching per CheckVC19List: {ts.TotalMilliseconds} ms"); } if (dbResult == null) { dbResult = new List(); } return await Task.FromResult(dbResult); } /// /// Recupera l'elenco dei dettagli giornalieri attività di un dipendente, dato periodo riferimento /// /// Dipendente interessato /// Data di riferimento (ultima/corrente) /// NUm settimane precedenti da recuperare /// public async Task> DailyDetails(int idxDipendente, DateTime dtInizio, DateTime dtFine) { List? dbResult = new List(); string currKey = $"{rKeyDailyData}:{idxDipendente}:{dtInizio.ToString("yyyy-MM-dd")}:{dtFine.ToString("yyyy-MM-dd")}"; trackCache(currKey); string rawData; var redisDataList = await distributedCache.GetAsync(currKey); if (redisDataList != null) { rawData = Encoding.UTF8.GetString(redisDataList); dbResult = JsonConvert.DeserializeObject>(rawData); } else { Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); dbResult = dbController.DailyDetails(idxDipendente, dtInizio, dtFine); rawData = JsonConvert.SerializeObject(dbResult, JSSettings); redisDataList = Encoding.UTF8.GetBytes(rawData); await distributedCache.SetAsync(currKey, redisDataList, cacheOpt(true)); stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; Log.Trace($"Effettuata lettura da DB + caching per DailyDetails: {ts.TotalMilliseconds} ms"); } if (dbResult == null) { dbResult = new List(); } return await Task.FromResult(dbResult); } public async Task> DipendentiGetAll() { List? dbResult = new List(); string rawData; var redisDataList = await distributedCache.GetAsync(rKeyDipendenti); if (redisDataList != null) { rawData = Encoding.UTF8.GetString(redisDataList); dbResult = JsonConvert.DeserializeObject>(rawData); } else { Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); dbResult = dbController.DipendentiGetAll(); rawData = JsonConvert.SerializeObject(dbResult, JSSettings); redisDataList = Encoding.UTF8.GetBytes(rawData); await distributedCache.SetAsync(rKeyDipendenti, redisDataList, cacheOpt(false)); stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; Log.Trace($"Effettuata lettura da DB + caching per DipendentiGetAll: {ts.TotalMilliseconds} ms"); } if (dbResult == null) { dbResult = new List(); } return await Task.FromResult(dbResult); } public void Dispose() { // Clear database controller dbController.Dispose(); } /// /// invalida tutta la cache in caso di update /// /// public async Task InvalidateAllCache() { await distributedCache.RemoveAsync(rKeyDipendenti); await distributedCache.RemoveAsync(rKeyFasiAll); foreach (var item in cachedDataList) { await distributedCache.RemoveAsync(item); } cachedDataList = new List(); } /// /// Invalida la cache corrispondente al apttern fornito /// /// public async Task InvalidateCache(string cachePattern) { foreach (var item in cachedDataList) { if (item.Contains(cachePattern)) { await distributedCache.RemoveAsync(item); } } } /// /// Statistiche ultime settimane /// /// /// /// /// public async Task> LastWeeks(int idxDipendente, DateTime dtRif, int numWeek) { List? dbResult = new List(); string currKey = $"{rKeyWeekStats}:{idxDipendente}:{dtRif.ToString("yyyy-MM-dd")}:{numWeek}"; trackCache(currKey); string rawData; var redisDataList = await distributedCache.GetAsync(currKey); if (redisDataList != null) { rawData = Encoding.UTF8.GetString(redisDataList); dbResult = JsonConvert.DeserializeObject>(rawData); } else { Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); dbResult = dbController.WeekOverview(idxDipendente, dtRif, numWeek); rawData = JsonConvert.SerializeObject(dbResult, JSSettings); redisDataList = Encoding.UTF8.GetBytes(rawData); await distributedCache.SetAsync(currKey, redisDataList, cacheOpt(true)); stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; Log.Trace($"Effettuata lettura da DB + caching per LastWeeks: {ts.TotalMilliseconds} ms"); } if (dbResult == null) { dbResult = new List(); } return await Task.FromResult(dbResult); } /// /// Elenco pareto progetti ordinati da filtro /// /// /// /// /// public async Task> ParetoRegAtt(int idxDip, int numDayPrev, int maxResult) { List? dbResult = new List(); string currKey = $"{rKeyParetoRegAtt}:{idxDip}:{numDayPrev}:{maxResult}"; DateTime dataFine = DateTime.Today.AddDays(1); DateTime dataInizio = dataFine.AddDays(-numDayPrev); string rawData; var redisDataList = await distributedCache.GetAsync(currKey); if (redisDataList != null) { rawData = Encoding.UTF8.GetString(redisDataList); dbResult = JsonConvert.DeserializeObject>(rawData); } else { Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); dbResult = dbController.ParetoRegAtt(idxDip, dataInizio, dataFine, maxResult); rawData = JsonConvert.SerializeObject(dbResult, JSSettings); redisDataList = Encoding.UTF8.GetBytes(rawData); await distributedCache.SetAsync(currKey, redisDataList, cacheOpt(true)); stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; Log.Trace($"Effettuata lettura da DB + caching per ParetoRegAtt: {ts.TotalMilliseconds} ms"); } if (dbResult == null) { dbResult = new List(); } return await Task.FromResult(dbResult); } public async Task RegAttDelete(RegAttivitaModel currItem) { bool answ = false; try { dbController.RegAttDelete(currItem); // invalido la cache... await InvalidateAllCache(); answ = true; } catch { } return answ; } /// /// Recupera ultima attività dipendente /// /// /// cerca ultima solo tra attive (=true) o tutte (=false) /// public async Task RegAttLastByDip(int IdxDipendente, bool onlyActive) { RegAttivitaModel dbResult = dbController.RegAttLastByDip(IdxDipendente, onlyActive); return await Task.FromResult(dbResult); } public async Task RegAttUpdate(RegAttivitaModel currItem) { bool answ = false; try { dbController.RegAttUpdate(currItem); // invalido la cache... await InvalidateAllCache(); answ = true; } catch { } return answ; } /// /// Recupera l'elenco dei Rilievi temperatura nel periodo indicato x dipendente /// /// Dipendente interessato /// Data di riferimento (ultima/corrente) /// NUm settimane precedenti da recuperare /// public async Task> RilTempList(int idxDipendente, DateTime dtInizio, DateTime dtFine) { List? dbResult = new List(); string currKey = $"{rKeyRilTemp}:{dtInizio:yyyyMMdd}:{dtFine:yyyMMdd}"; trackCache(currKey); string rawData; var redisDataList = await distributedCache.GetAsync(currKey); if (redisDataList != null) { rawData = Encoding.UTF8.GetString(redisDataList); dbResult = JsonConvert.DeserializeObject>(rawData); } else { Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); dbResult = dbController.RilTempList(idxDipendente, dtInizio, dtFine); rawData = JsonConvert.SerializeObject(dbResult, JSSettings); redisDataList = Encoding.UTF8.GetBytes(rawData); await distributedCache.SetAsync(currKey, redisDataList, cacheOpt(true)); stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; Log.Trace($"Effettuata lettura da DB + caching per RilTempList: {ts.TotalMilliseconds} ms"); } if (dbResult == null) { dbResult = new List(); } return await Task.FromResult(dbResult); } public async Task RilTempUpdate(RilievoTempModel currItem) { bool answ = false; try { dbController.RilTempUpdate(currItem); // invalido la cache... await InvalidateCache(rKeyRilTemp); //await InvalidateCache($"{rKeyWeekStats}:{currItem.IdxDipendente}"); await InvalidateCache($"{rKeyDailyData}:{currItem.IdxDipendente}"); answ = true; } catch { } return answ; } public void rollBackEdit(object item) { dbController.rollBackEntity(item); } public async Task TimbratureDelete(TimbratureModel currItem) { bool answ = false; try { dbController.TimbratureDelete(currItem); // invalido la cache... await InvalidateAllCache(); answ = true; } catch { } return answ; } public async Task TimbratureUpdate(TimbratureModel currItem) { bool answ = false; try { dbController.TimbratureUpdate(currItem); // invalido la cache... await InvalidateAllCache(); answ = true; } catch { } return answ; } #endregion Public Methods } }