diff --git a/MP.Data/DbModels/Utils/StatsAggregatedModel.cs b/MP.Data/DbModels/Utils/StatsAggregatedModel.cs
new file mode 100644
index 00000000..4221288b
--- /dev/null
+++ b/MP.Data/DbModels/Utils/StatsAggregatedModel.cs
@@ -0,0 +1,47 @@
+//
+// This is here so CodeMaid doesn't reorganize this document
+//
+namespace MP.Data.DbModels.Utils
+{
+ ///
+ /// Classe statistiche impiego orario complessive
+ ///
+ [Table("stats_aggr")]
+ public class StatsAggregatedModel
+ {
+ [Key]
+ public int Id { get; set; }
+ ///
+ /// DataOra riferimento
+ ///
+ public DateTime Hour { get; set; }
+ ///
+ /// Num richieste complete/concluse (con ritorno)
+ ///
+ public long RequestCount { get; set; } = 0;
+ ///
+ /// Durata media esecuzione
+ ///
+ public double AvgDuration { get; set; } = 0;
+ ///
+ /// Durata massima esecuzione
+ ///
+ public double MaxDuration { get; set; } = 0;
+ ///
+ /// Durata 5 perc esecuzione
+ ///
+ public double Perc05Duration { get; set; } = 0;
+ ///
+ /// Durata 95 perc esecuzione
+ ///
+ public double Perc95Duration { get; set; } = 0;
+ ///
+ /// Durata massima esecuzione
+ ///
+ public double MaxDuration { get; set; } = 0;
+ ///
+ /// numero richieste senza risposta (num RUID avviati - num RUID chiusi)
+ ///
+ public long NoReply { get; set; } = 0;
+ }
+}
diff --git a/MP.Data/DbModels/Utils/StatsDetailModel.cs b/MP.Data/DbModels/Utils/StatsDetailModel.cs
new file mode 100644
index 00000000..0aa9b4a1
--- /dev/null
+++ b/MP.Data/DbModels/Utils/StatsDetailModel.cs
@@ -0,0 +1,51 @@
+//
+// This is here so CodeMaid doesn't reorganize this document
+//
+namespace MP.Data.DbModels.Utils
+{
+ ///
+ /// Classe statistiche impiego (orarie) di dettaglio (per envir/tipo)
+ ///
+ [Table("stats_detail")]
+ public class StatsDetailModel
+ {
+ [Key]
+ public int Id { get; set; }
+ ///
+ /// Ambiente
+ ///
+ public string Environment { get; set; } = "";
+ ///
+ /// Codice mode/submode
+ ///
+ public string Type { get; set; } = "";
+ ///
+ /// DataOra riferimento
+ ///
+ public DateTime Hour { get; set; }
+ ///
+ /// Num richieste complete/concluse (con ritorno)
+ ///
+ public long RequestCount { get; set; } = 0;
+ ///
+ /// Durata media esecuzione
+ ///
+ public double AvgDuration { get; set; } = 0;
+ ///
+ /// Durata 5 perc esecuzione
+ ///
+ public double Perc05Duration { get; set; } = 0;
+ ///
+ /// Durata 95 perc esecuzione
+ ///
+ public double Perc95Duration { get; set; } = 0;
+ ///
+ /// Durata massima esecuzione
+ ///
+ public double MaxDuration { get; set; } = 0;
+ ///
+ /// numero richieste senza risposta (num RUID avviati - num RUID chiusi)
+ ///
+ public long NoReply { get; set; } = 0;
+ }
+}
diff --git a/MP.Data/MoonPro_UtilsContext.cs b/MP.Data/MoonPro_UtilsContext.cs
new file mode 100644
index 00000000..b87aad6c
--- /dev/null
+++ b/MP.Data/MoonPro_UtilsContext.cs
@@ -0,0 +1,131 @@
+using System;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.Extensions.Configuration;
+using MP.Core.DTO;
+using MP.Data.DbModels;
+using MP.Data.DTO;
+using NLog;
+
+#nullable disable
+//
+// This is here so CodeMaid doesn't reorganize this document
+//
+namespace MP.Data
+{
+ public partial class MoonPro_UtilsContext : DbContext
+ {
+ #region Private Fields
+
+ private static NLog.Logger Log = LogManager.GetCurrentClassLogger();
+
+ private IConfiguration _configuration;
+
+ #endregion Private Fields
+
+ #region Public Constructors
+
+ ///
+ /// Indispensabile x prima generazione migrations EFCore
+ ///
+
+ [Obsolete("This constructor should never be used directly, and is only needed to generate entityframework stuff. Connection string can be adapted as pleased.")]
+ public MoonPro_UtilsContext()
+ {
+ }
+
+ public MoonPro_UtilsContext(IConfiguration configuration)
+ {
+ _configuration = configuration;
+ }
+
+ public MoonPro_UtilsContext(DbContextOptions options) : base(options)
+ {
+ }
+
+ #endregion Public Constructors
+
+ #region Public Properties
+
+
+ public virtual DbSet DbSetStatsDet { get; set; }
+ public virtual DbSet DbSetStatsAggr { get; set; }
+
+ #endregion Public Properties
+
+ #region Private Methods
+
+ partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
+
+ #endregion Private Methods
+
+ #region Protected Methods
+
+ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
+ {
+ if (!optionsBuilder.IsConfigured)
+ {
+ string connString = _configuration.GetConnectionString("MP.Utils");
+ if (!string.IsNullOrEmpty(connString))
+ {
+ optionsBuilder.UseSqlServer(connString);
+ }
+ else
+ {
+ optionsBuilder.UseSqlServer("Server=SQL2016DEV;Database=MoonPro_Utils;Trusted_Connection=True;");
+ }
+ }
+ }
+
+ protected override void OnModelCreating(ModelBuilder modelBuilder)
+ {
+ modelBuilder.HasAnnotation("Relational:Collation", "SQL_Latin1_General_CP1_CI_AS");
+
+ // indici tabelle stats
+ modelBuilder.Entity()
+ .HasIndex(x => new { x.Hour, x.Environment, x.Type })
+ .HasDatabaseName("idx_statsdet_hour_env_type")
+ .IsUnique();
+ modelBuilder.Entity()
+ .HasIndex(x => new { x.Hour })
+ .HasDatabaseName("idx_statsaggr_hour")
+ .IsUnique();
+
+ // modelBuilder.Entity(entity =>
+ // {
+ // entity.HasKey(e => e.CodArticolo);
+
+ // entity.ToView("AnagArticoli");
+
+ // entity.Property(e => e.CodArticolo)
+ // .IsRequired()
+ // .HasMaxLength(50);
+
+ // entity.Property(e => e.DescArticolo)
+ // .IsRequired()
+ // .HasMaxLength(250);
+
+ // entity.Property(e => e.Disegno)
+ // .IsRequired()
+ // .HasMaxLength(50);
+
+ // entity.Property(e => e.Tipo)
+ // .IsRequired()
+ // .HasMaxLength(50);
+
+ // entity.Property(e => e.Azienda)
+ // .IsRequired()
+ // .HasMaxLength(50);
+ // });
+
+ // modelBuilder.Entity(entity =>
+ // {
+ // entity.HasKey(e => new { e.IdxMacchina, e.CodFlux });
+ // });
+
+ OnModelCreatingPartial(modelBuilder);
+ }
+
+ #endregion Protected Methods
+ }
+}
\ No newline at end of file
diff --git a/MP.Data/Repository/Utils/IStatsAggrRepository.cs b/MP.Data/Repository/Utils/IStatsAggrRepository.cs
new file mode 100644
index 00000000..5f2bf909
--- /dev/null
+++ b/MP.Data/Repository/Utils/IStatsAggrRepository.cs
@@ -0,0 +1,32 @@
+using EgwCoreLib.Utils;
+
+namespace MP.Data.Repository.Utils
+{
+ ///
+ /// Interfaccia per le statistiche aggregate.
+ ///
+ public interface IStatsAggrRepository
+ {
+ ///
+ /// Recupera l'elenco delle statistiche aggregate per un periodo specifico.
+ ///
+ /// La data di inizio del periodo.
+ /// La data di fine del periodo.
+ /// L'elenco delle statistiche aggregate ordinate cronologicamente.
+ Task> GetFiltAsync(DateTime dtStart, DateTime dtEnd);
+
+ ///
+ /// Recupera l'intervallo temporale disponibile nel database per le statistiche aggregate.
+ ///
+ /// L'intervallo di date (minima e massima ora presente).
+ Task GetRangeAsync();
+
+ ///
+ /// Inserisce o aggiorna in blocco le statistiche aggregate nel database.
+ ///
+ /// L'elenco dei record da inserire.
+ /// Se true, elimina preventivamente i record nel periodo richiesto.
+ /// Il numero di record inseriti.
+ Task UpsertManyAsync(List listRecords, bool removeOld);
+ }
+}
\ No newline at end of file
diff --git a/MP.Data/Repository/Utils/IStatsDetailRepository.cs b/MP.Data/Repository/Utils/IStatsDetailRepository.cs
new file mode 100644
index 00000000..3db2182c
--- /dev/null
+++ b/MP.Data/Repository/Utils/IStatsDetailRepository.cs
@@ -0,0 +1,36 @@
+using EgwCoreLib.Utils;
+
+namespace MP.Data.Repository.Utils
+{
+ ///
+ /// Interfaccia per le statistiche di dettaglio.
+ ///
+ public interface IStatsDetailRepository
+ {
+ ///
+ /// Recupera l'elenco delle statistiche di dettaglio per un periodo specifico, con filtri opzionali.
+ ///
+ /// La data di inizio del periodo.
+ /// La data di fine del periodo.
+ /// Filtro opzionale per l'ambiente (es. "DEV", "PROD").
+ /// Filtro opzionale per il tipo di statistica.
+ /// L'elenco delle statistiche di dettaglio ordinate cronologicamente.
+ Task> GetFiltAsync(DateTime dtStart, DateTime dtEnd, string sEnvir = "", string sType = "");
+
+ ///
+ /// Recupera l'intervallo temporale disponibile nel database per le statistiche di dettaglio.
+ ///
+ /// Filtro opzionale per l'ambiente.
+ /// Filtro opzionale per il tipo.
+ /// L'intervallo di date (minima e massima ora presente).
+ Task GetRangeAsync(string sEnvir, string sType);
+
+ ///
+ /// Inserisce o aggiorna in blocco le statistiche di dettaglio nel database.
+ ///
+ /// L'elenco dei record da inserire.
+ /// Se true, elimina preventivamente i record nel periodo richiesto.
+ /// Il numero di record inseriti.
+ Task UpsertManyAsync(List listRecords, bool removeOld);
+ }
+}
\ No newline at end of file
diff --git a/MP.Data/Repository/Utils/StatsAggrRepository.cs b/MP.Data/Repository/Utils/StatsAggrRepository.cs
new file mode 100644
index 00000000..2f9e6657
--- /dev/null
+++ b/MP.Data/Repository/Utils/StatsAggrRepository.cs
@@ -0,0 +1,93 @@
+using EgwCoreLib.Utils;
+
+namespace MP.Data.Repository.Utils
+{
+ public class StatsAggrRepository : BaseRepository, IStatsAggrRepository
+ {
+ #region Public Constructors
+
+ public StatsAggrRepository(IDbContextFactory ctxFactory) : base(ctxFactory)
+ {
+ }
+
+ #endregion Public Constructors
+
+ #region Public Methods
+
+ ///
+ public async Task> GetFiltAsync(DateTime dtStart, DateTime dtEnd)
+ {
+ await using var dbCtx = await CreateContextAsync();
+ return await dbCtx
+ .DbSetStatsAggr
+ .Where(x => x.Hour >= dtStart && x.Hour <= dtEnd)
+ .AsNoTracking()
+ .OrderBy(x => x.Hour)
+ .ToListAsync();
+ }
+
+ ///
+ public async Task GetRangeAsync()
+ {
+ await using var dbCtx = await CreateContextAsync();
+ DtUtils.Periodo answ = new DtUtils.Periodo(DtUtils.PeriodSet.Today);
+ var query = dbCtx.DbSetStatsAggr.AsQueryable();
+ var minHour = await query.MinAsync(x => x.Hour);
+ var maxHour = await query.MaxAsync(x => x.Hour);
+ answ.Inizio = minHour;
+ answ.Fine = maxHour;
+ // ritorno!
+ return answ;
+ }
+
+ ///
+ public async Task UpsertManyAsync(List listRecords, bool removeOld)
+ {
+ int answ = 0;
+ await using var dbCtx = await CreateContextAsync();
+ await using var tx = await dbCtx.Database.BeginTransactionAsync();
+ try
+ {
+ // in primis se richiesto calcolo range periodo e svuoto...
+ if (removeOld)
+ {
+ var firstRec = listRecords.OrderBy(x => x.Hour).FirstOrDefault();
+ var lastRec = listRecords.OrderByDescending(x => x.Hour).FirstOrDefault();
+
+ if (firstRec != null && lastRec != null)
+ {
+ DateTime startDate = firstRec.Hour;
+ DateTime endDate = lastRec.Hour;
+ // uso direttamente ExecuteDelete
+ await dbCtx
+ .DbSetStatsAggr
+ .Where(x => x.Hour >= startDate && x.Hour <= endDate)
+ .ExecuteDeleteAsync();
+ }
+ }
+
+ // ora preparo inserimento massivo
+ await dbCtx
+ .DbSetStatsAggr
+ .AddRangeAsync(listRecords);
+
+ // salvo!
+ answ = await dbCtx.SaveChangesAsync();
+
+ // commit transazione
+ await tx.CommitAsync();
+
+ // libero memoria del changeTracker
+ dbCtx.ChangeTracker.Clear();
+ return answ;
+ }
+ catch
+ {
+ await tx.RollbackAsync();
+ throw;
+ }
+ }
+
+ #endregion Public Methods
+ }
+}
\ No newline at end of file
diff --git a/MP.Data/Repository/Utils/StatsDetailRepository.cs b/MP.Data/Repository/Utils/StatsDetailRepository.cs
new file mode 100644
index 00000000..4b09d801
--- /dev/null
+++ b/MP.Data/Repository/Utils/StatsDetailRepository.cs
@@ -0,0 +1,113 @@
+using EgwCoreLib.Utils;
+
+namespace MP.Data.Repository.Utils
+{
+ public class StatsDetailRepository : BaseRepository, IStatsDetailRepository
+ {
+ #region Public Constructors
+
+ public StatsDetailRepository(IDbContextFactory ctxFactory) : base(ctxFactory)
+ {
+ }
+
+ #endregion Public Constructors
+
+ #region Internal Methods
+
+ ///
+ public async Task> GetFiltAsync(DateTime dtStart, DateTime dtEnd, string sEnvir = "", string sType = "")
+ {
+ await using var dbCtx = await CreateContextAsync();
+ List answ = new List();
+
+ // recupero ed ordino per data-ora
+ var query = dbCtx.DbSetStatsDet
+ .Where(x => x.Hour >= dtStart && x.Hour <= dtEnd);
+
+ if (!string.IsNullOrEmpty(sEnvir))
+ query = query.Where(x => x.Environment == sEnvir);
+
+ if (!string.IsNullOrEmpty(sType))
+ query = query.Where(x => x.Type == sType);
+
+ answ = await query
+ .AsNoTracking()
+ .OrderBy(x => x.Hour)
+ .ThenBy(x => x.Environment)
+ .ThenBy(x => x.Type)
+ .ToListAsync();
+
+ return answ;
+ }
+
+ ///
+ public async Task GetRangeAsync(string sEnvir, string sType)
+ {
+ await using var dbCtx = await CreateContextAsync();
+ DtUtils.Periodo answ = new DtUtils.Periodo(DtUtils.PeriodSet.Today);
+
+ var query = dbCtx.DbSetStatsDet.AsQueryable();
+
+ if (!string.IsNullOrEmpty(sEnvir))
+ query = query.Where(x => x.Environment == sEnvir);
+
+ if (!string.IsNullOrEmpty(sType))
+ query = query.Where(x => x.Type == sType);
+
+ var minHour = await query.MinAsync(x => x.Hour);
+ var maxHour = await query.MaxAsync(x => x.Hour);
+ answ.Inizio = minHour;
+ answ.Fine = maxHour;
+ return answ;
+ }
+
+ ///
+ public async Task UpsertManyAsync(List listRecords, bool removeOld)
+ {
+ int answ = 0;
+ await using var dbCtx = await CreateContextAsync();
+ await using var tx = await dbCtx.Database.BeginTransactionAsync();
+ try
+ {
+ // in primis se richiesto calcolo range periodo e svuoto...
+ if (removeOld)
+ {
+ var firstRec = listRecords.OrderBy(x => x.Hour).FirstOrDefault();
+ var lastRec = listRecords.OrderByDescending(x => x.Hour).FirstOrDefault();
+
+ if (firstRec != null && lastRec != null)
+ {
+ DateTime startDate = firstRec.Hour;
+ DateTime endDate = lastRec.Hour;
+ // uso direttamente ExecuteDelete
+ await dbCtx
+ .DbSetStatsDet
+ .Where(x => x.Hour >= startDate && x.Hour <= endDate)
+ .ExecuteDeleteAsync();
+ }
+ }
+
+ // ora preparo inserimento massivo
+ await dbCtx
+ .DbSetStatsDet
+ .AddRangeAsync(listRecords);
+
+ // salvo!
+ answ = await dbCtx.SaveChangesAsync();
+ // commit transazione
+ await tx.CommitAsync();
+
+ // libero memoria del changeTracker
+ dbCtx.ChangeTracker.Clear();
+ return answ;
+ }
+ catch
+ {
+ await tx.RollbackAsync();
+ throw;
+ }
+ }
+
+ #endregion Internal Methods
+ }
+}
\ No newline at end of file
diff --git a/MP.Data/Services/Utils/IStatsAggrService.cs b/MP.Data/Services/Utils/IStatsAggrService.cs
new file mode 100644
index 00000000..d38a0403
--- /dev/null
+++ b/MP.Data/Services/Utils/IStatsAggrService.cs
@@ -0,0 +1,31 @@
+namespace MP.Data.Services.Utils
+{
+ public interface IStatsAggrService
+ {
+ #region Public Methods
+
+ ///
+ /// Recupera l'elenco delle statistiche aggregate per un periodo specificato.
+ /// Utilizza la cache automaticamente.
+ ///
+ /// Data inizio periodo
+ /// Data fine periodo
+ Task> GetFiltAsync(DateTime dtStart, DateTime dtEnd);
+
+ ///
+ /// Recupera il range di periodi valido per le chiamate aggregate.
+ /// Utilizza la cache automaticamente.
+ ///
+ Task GetRangeAsync();
+
+ ///
+ /// Inserisce o aggiorna in batch le statistiche aggregate nel database.
+ /// Opzionalmente elimina i record precedenti nel periodo specificato.
+ ///
+ /// Elenco dei record da inserire/aggiornare
+ /// Se true elimina preventivamente i record nel periodo richiesto
+ Task UpsertManyAsync(List listRecords, bool removeOld);
+
+ #endregion Public Methods
+ }
+}
\ No newline at end of file
diff --git a/MP.Data/Services/Utils/IStatsDetailService.cs b/MP.Data/Services/Utils/IStatsDetailService.cs
new file mode 100644
index 00000000..1eb399da
--- /dev/null
+++ b/MP.Data/Services/Utils/IStatsDetailService.cs
@@ -0,0 +1,35 @@
+namespace MP.Data.Services.Utils
+{
+ public interface IStatsDetailService
+ {
+ #region Public Methods
+
+ ///
+ /// Recupera le statistiche di dettaglio per un periodo specificato, con filtri opzionali su ambiente e tipo.
+ /// Utilizza la cache automaticamente.
+ ///
+ /// Data inizio periodo
+ /// Data fine periodo
+ /// Filtro opzionale per ambiente (default: vuoto = tutti)
+ /// Filtro opzionale per tipo (default: vuoto = tutti)
+ Task> GetFiltAsync(DateTime dtStart, DateTime dtEnd, string sEnvir = "", string sType = "");
+
+ ///
+ /// Recupera il range di periodi valido per le chiamate di dettaglio.
+ /// Utilizza la cache automaticamente.
+ ///
+ /// Ambiente filtrato
+ /// Tipo filtrato
+ Task GetRangeAsync(string sEnvir, string sType);
+
+ ///
+ /// Inserisce o aggiorna in batch le statistiche di dettaglio nel database.
+ /// Opzionalmente elimina i record precedenti nel periodo specificato.
+ ///
+ /// Elenco dei record da inserire/aggiornare
+ /// Se true elimina preventivamente i record nel periodo richiesto
+ Task UpsertManyAsync(List listRecords, bool removeOld);
+
+ #endregion Public Methods
+ }
+}
\ No newline at end of file
diff --git a/MP.Data/Services/Utils/StatsAggrService.cs b/MP.Data/Services/Utils/StatsAggrService.cs
new file mode 100644
index 00000000..2d6ec1ff
--- /dev/null
+++ b/MP.Data/Services/Utils/StatsAggrService.cs
@@ -0,0 +1,74 @@
+namespace MP.Data.Services.Utils
+{
+ public class StatsAggrService : BaseServ, IStatsAggrService
+ {
+ #region Public Constructors
+
+ public StatsAggrService(
+ IConfiguration config,
+ IConnectionMultiplexer redis,
+ IStatsAggrRepository repo) : base(config, redis)
+ {
+ _className = "StatsAggr";
+ _repo = repo;
+ }
+
+ #endregion Public Constructors
+
+ #region Public Methods
+
+ ///
+ public async Task> GetFiltAsync(DateTime dtStart, DateTime dtEnd)
+ {
+ return await TraceAsync($"{_className}.GetFilt", async (activity) =>
+ {
+ return await GetOrSetCacheAsync(
+ $"{_redisBaseKey}:{_className}:DT:{dtStart:yyyyMMdd}:{dtEnd:yyyyMMdd}",
+ async () => await _repo.GetFiltAsync(dtStart, dtEnd),
+ UltraLongCache
+ );
+ });
+ }
+
+ ///
+ public async Task GetRangeAsync()
+ {
+ return await TraceAsync($"{_className}.GetRange", async (activity) =>
+ {
+ return await GetOrSetCacheAsync(
+ $"{_redisBaseKey}:{_className}:Range",
+ async () => await _repo.GetRangeAsync(),
+ UltraFastCache
+ );
+ });
+ }
+
+ ///
+ public async Task UpsertManyAsync(List listRecords, bool removeOld)
+ {
+ return await TraceAsync($"{_className}.UpsertMany", async (activity) =>
+ {
+ string operation = "UpsertMany";
+ var success = await _repo.UpsertManyAsync(listRecords, removeOld);
+
+ activity?.SetTag("db.operation", operation);
+
+ if (success > 0)
+ {
+ await ClearCacheAsync($"{_redisBaseKey}:{_className}:*");
+ }
+
+ return success;
+ });
+ }
+
+ #endregion Public Methods
+
+ #region Private Fields
+
+ private readonly string _className;
+ private readonly IStatsAggrRepository _repo;
+
+ #endregion Private Fields
+ }
+}
\ No newline at end of file
diff --git a/MP.Data/Services/Utils/StatsDetailService.cs b/MP.Data/Services/Utils/StatsDetailService.cs
new file mode 100644
index 00000000..43a9d2fe
--- /dev/null
+++ b/MP.Data/Services/Utils/StatsDetailService.cs
@@ -0,0 +1,74 @@
+namespace MP.Data.Services.Utils
+{
+ public class StatsDetailService : BaseServ, IStatsDetailService
+ {
+ #region Public Constructors
+
+ public StatsDetailService(
+ IConfiguration config,
+ IConnectionMultiplexer redis,
+ IStatsDetailRepository repo) : base(config, redis)
+ {
+ _className = "StatsDetail";
+ _repo = repo;
+ }
+
+ #endregion Public Constructors
+
+ #region Public Methods
+
+ ///
+ public async Task> GetFiltAsync(DateTime dtStart, DateTime dtEnd, string sEnvir = "", string sType = "")
+ {
+ return await TraceAsync($"{_className}.GetFilt", async (activity) =>
+ {
+ return await GetOrSetCacheAsync(
+ $"{_redisBaseKey}:{_className}:DT:{dtStart:yyyyMMdd}:{dtEnd:yyyyMMdd}",
+ async () => await _repo.GetFiltAsync(dtStart, dtEnd),
+ UltraLongCache
+ );
+ });
+ }
+
+ ///
+ public async Task GetRangeAsync(string sEnvir, string sType)
+ {
+ return await TraceAsync($"{_className}.GetRange", async (activity) =>
+ {
+ return await GetOrSetCacheAsync(
+ $"{_redisBaseKey}:{_className}:Range:{sEnvir}:{sType}",
+ async () => await _repo.GetRangeAsync(sEnvir, sType),
+ UltraFastCache
+ );
+ });
+ }
+
+ ///
+ public async Task UpsertManyAsync(List listRecords, bool removeOld)
+ {
+ return await TraceAsync($"{_className}.UpsertMany", async (activity) =>
+ {
+ string operation = "UpsertMany";
+ var success = await _repo.UpsertManyAsync(listRecords, removeOld);
+
+ activity?.SetTag("db.operation", operation);
+
+ if (success > 0)
+ {
+ await ClearCacheAsync($"{_redisBaseKey}:{_className}:*");
+ }
+
+ return success;
+ });
+ }
+
+ #endregion Public Methods
+
+ #region Private Fields
+
+ private readonly string _className;
+ private readonly IStatsDetailRepository _repo;
+
+ #endregion Private Fields
+ }
+}
\ No newline at end of file
diff --git a/MP.IOC/appsettings.json b/MP.IOC/appsettings.json
index 042c4fef..5587b2d4 100644
--- a/MP.IOC/appsettings.json
+++ b/MP.IOC/appsettings.json
@@ -70,9 +70,7 @@
}
},
"RouteMan": {
- "FlushIntervalSeconds": 10,
- //"FlushIntervalSeconds": 60,
- //"FlushIntervalSeconds": 300,
+ "FlushIntervalSeconds": 60,
"DefaultWeightOld": 100,
"DefaultWeightNew": 0
},
@@ -84,6 +82,7 @@
},
"ConnectionStrings": {
"MP.Data": "Server=SQL2016DEV;Database=MoonPro; User ID=sa;Password=keyhammer16; integrated security=False; MultipleActiveResultSets=True; App=MP.IOC;",
+ "MP.Utils": "Server=SQL2016DEV;Database=MoonPro_Utils; User ID=sa;Password=keyhammer16; integrated security=False; MultipleActiveResultSets=True; App=MP.IOC;",
"Redis": "redis.ufficio:26379,serviceName=devel,DefaultDatabase=5,connectTimeout=5000,syncTimeout=5000,asyncTimeout=5000,abortConnect=false,ssl=false",
"RedisAdmin": "redis.ufficio:26379,serviceName=devel,DefaultDatabase=5,connectTimeout=5000,syncTimeout=5000,asyncTimeout=5000,abortConnect=false,ssl=false,allowAdmin=true",
"mdbConnString": "mongodb://W2019-MONGODB:27017"