From d83353bc82197ad94f204fd608749cb1364938ac Mon Sep 17 00:00:00 2001 From: Samuele Locatelli Date: Wed, 8 Apr 2026 19:33:03 +0200 Subject: [PATCH] Upsert con merge x dati aggregati (da provare) --- MP.Data/Repository/BaseRepository.cs | 8 +- .../Repository/Utils/StatsAggrRepository.cs | 86 +++++++++++++++++++ .../Repository/Utils/StatsDetailRepository.cs | 13 ++- MP.IOC/Resources/ChangeLog.html | 2 +- MP.IOC/Resources/VersNum.txt | 2 +- MP.IOC/Resources/manifest.xml | 2 +- MP.IOC/Services/MetricsDbFlushService.cs | 2 +- MP.IOC/Services/RouteManager.cs | 2 +- 8 files changed, 105 insertions(+), 12 deletions(-) diff --git a/MP.Data/Repository/BaseRepository.cs b/MP.Data/Repository/BaseRepository.cs index 4c20bcf4..7afc1bd2 100644 --- a/MP.Data/Repository/BaseRepository.cs +++ b/MP.Data/Repository/BaseRepository.cs @@ -27,21 +27,21 @@ namespace MP.Data.Repository protected async Task CreateContextAsync() => await _ctxFactory.CreateDbContextAsync(); + #endregion Protected Methods + #if false /// /// Salvataggio dati asincrono /// /// protected async Task SaveChangesAsync(DataLayerContext ctx) - => await ctx.SaveChangesAsync() > 0; + => await ctx.SaveChangesAsync() > 0; #endif - #endregion Protected Methods - #if false protected readonly DataLayerContext _dbCtx; protected BaseRepository(DataLayerContext db) => _dbCtx = db; public async Task SaveChangesAsync() => await _dbCtx.SaveChangesAsync() > 0; #endif } -} +} \ No newline at end of file diff --git a/MP.Data/Repository/Utils/StatsAggrRepository.cs b/MP.Data/Repository/Utils/StatsAggrRepository.cs index ee63c073..1f7b5cb2 100644 --- a/MP.Data/Repository/Utils/StatsAggrRepository.cs +++ b/MP.Data/Repository/Utils/StatsAggrRepository.cs @@ -1,6 +1,7 @@ using EgwCoreLib.Utils; using Microsoft.EntityFrameworkCore; using MP.Data.DbModels.Utils; +using NLog; using System; using System.Collections.Generic; using System.Linq; @@ -48,6 +49,85 @@ namespace MP.Data.Repository.Utils /// public async Task UpsertManyAsync(List listRecords, bool removeOld) + { + if (listRecords == null || !listRecords.Any()) return 0; + + int ans = 0; + await using var dbCtx = await CreateContextAsync(); + await using var tx = await dbCtx.Database.BeginTransactionAsync(); + + try + { + // 1. Calcolo del range temporale della lista in arrivo per limitare la query di ricerca + var minHour = listRecords.Min(x => x.Hour); + var maxHour = listRecords.Max(x => x.Hour); + + // 2. Se removeOld è true, manteniamo la logica originale (Eliminazione distruttiva) + if (removeOld) + { + var itemsToRemove = await dbCtx.DbSetStatsAggr + .Where(x => x.Hour >= minHour && x.Hour <= maxHour) + .ToListAsync(); + if (itemsToRemove.Any()) + { + dbCtx.DbSetStatsAggr.RemoveRange(itemsToRemove); + await dbCtx.SaveChangesAsync(); // Commit parziale per la cancellazione + } + } + + // 3. LOGICA DI UPSERT (Merge) + // Recuperiamo tutti i record esistenti nel database che cadono nello stesso range temporale + // Questo ci permette di confrontare ciò che arriva con ciò che è già presente. + var existingRecords = await dbCtx.DbSetStatsAggr + .Where(x => x.Hour >= minHour && x.Hour <= maxHour) + .ToListAsync(); + + // Creiamo un dizionario per ricerca rapida O(1) basato sulla chiave univoca (Dest + Hour) + // Usiamo una Tupla come chiave del dizionario + var lookup = existingRecords.ToDictionary( + x => (x.Destination, x.Hour), + x => x + ); + + foreach (var incoming in listRecords) + { + var key = (incoming.Destination, incoming.Hour); + if (lookup.TryGetValue(key, out var existing)) + { + // --- CASO: UPDATE --- + existing.RequestCount = incoming.RequestCount; + existing.AvgDuration = incoming.AvgDuration; + existing.MinDuration = incoming.MinDuration; + existing.MaxDuration = incoming.MaxDuration; + // Aggiungi altri campi se presenti (es. NoReply, ecc.) + } + else + { + // --- CASO: INSERT --- + await dbCtx.DbSetStatsAggr.AddAsync(incoming); + } + } + // 4. Salvataggio finale + ans = await dbCtx.SaveChangesAsync(); + + // Commit della transazione + await tx.CommitAsync(); + + // Pulizia memoria per evitare che il ChangeTracker diventi troppo pesante nei loop lunghi + dbCtx.ChangeTracker.Clear(); + + return ans; + } + catch (Exception ex) + { + await tx.RollbackAsync(); + Log.Error(ex, "Error during UpsertManyAsync"); + throw; + } + } + + /// + public async Task UpsertManyAsyncOrig(List listRecords, bool removeOld) { int answ = 0; await using var dbCtx = await CreateContextAsync(); @@ -103,5 +183,11 @@ namespace MP.Data.Repository.Utils } #endregion Public Methods + + #region Protected Fields + + protected static NLog.Logger Log = LogManager.GetCurrentClassLogger(); + + #endregion Protected Fields } } \ No newline at end of file diff --git a/MP.Data/Repository/Utils/StatsDetailRepository.cs b/MP.Data/Repository/Utils/StatsDetailRepository.cs index 603c9fff..ea3408c6 100644 --- a/MP.Data/Repository/Utils/StatsDetailRepository.cs +++ b/MP.Data/Repository/Utils/StatsDetailRepository.cs @@ -1,6 +1,7 @@ using EgwCoreLib.Utils; using Microsoft.EntityFrameworkCore; using MP.Data.DbModels.Utils; +using NLog; using System; using System.Collections.Generic; using System.Linq; @@ -18,7 +19,7 @@ namespace MP.Data.Repository.Utils #endregion Public Constructors - #region Internal Methods + #region Public Methods /// public async Task> GetFiltAsync(DateTime dtStart, DateTime dtEnd, string sEnvir = "", string sType = "") @@ -91,7 +92,7 @@ namespace MP.Data.Repository.Utils await dbCtx .DbSetStatsDet .Where(x => x.Hour >= startDate && x.Hour <= endDate) - .ExecuteDeleteAsync(); + .ExecuteDeleteAsync(); #endif var items = await dbCtx.DbSetStatsDet .Where(x => x.Hour >= startDate && x.Hour <= endDate) @@ -122,6 +123,12 @@ namespace MP.Data.Repository.Utils } } - #endregion Internal Methods + #endregion Public Methods + + #region Protected Fields + + protected static NLog.Logger Log = LogManager.GetCurrentClassLogger(); + + #endregion Protected Fields } } \ No newline at end of file diff --git a/MP.IOC/Resources/ChangeLog.html b/MP.IOC/Resources/ChangeLog.html index 520702ac..699fcbc7 100644 --- a/MP.IOC/Resources/ChangeLog.html +++ b/MP.IOC/Resources/ChangeLog.html @@ -1,6 +1,6 @@ Modulo MP-IOC -

Versione: 6.16.2604.817

+

Versione: 6.16.2604.819


Note di rilascio:
  • diff --git a/MP.IOC/Resources/VersNum.txt b/MP.IOC/Resources/VersNum.txt index 30a954ce..d94d6cf4 100644 --- a/MP.IOC/Resources/VersNum.txt +++ b/MP.IOC/Resources/VersNum.txt @@ -1 +1 @@ -6.16.2604.817 +6.16.2604.819 diff --git a/MP.IOC/Resources/manifest.xml b/MP.IOC/Resources/manifest.xml index 21d0d42d..6576570a 100644 --- a/MP.IOC/Resources/manifest.xml +++ b/MP.IOC/Resources/manifest.xml @@ -1,6 +1,6 @@ - 6.16.2604.817 + 6.16.2604.819 https://nexus.steamware.net/repository/SWS/MP-IOC/stable/LAST/MP.IOC.zip https://nexus.steamware.net/repository/SWS/MP-IOC/stable/LAST/ChangeLog.html false diff --git a/MP.IOC/Services/MetricsDbFlushService.cs b/MP.IOC/Services/MetricsDbFlushService.cs index 26b02ca2..9e4eddaa 100644 --- a/MP.IOC/Services/MetricsDbFlushService.cs +++ b/MP.IOC/Services/MetricsDbFlushService.cs @@ -168,7 +168,7 @@ namespace MP.IOC.Services { await using var scope = _scopeFactory.CreateAsyncScope(); var aggrService = scope.ServiceProvider.GetRequiredService(); - await aggrService.UpsertManyAsync(aggrRecordsToInsert, true); + await aggrService.UpsertManyAsync(aggrRecordsToInsert, false); Log.Info($"[DAY] Upserted {aggrRecordsToInsert.Count} records to DB"); } diff --git a/MP.IOC/Services/RouteManager.cs b/MP.IOC/Services/RouteManager.cs index fe29be9e..da8be532 100644 --- a/MP.IOC/Services/RouteManager.cs +++ b/MP.IOC/Services/RouteManager.cs @@ -112,7 +112,7 @@ namespace MP.IOC.Services // opzionale: se vuoi che PathBase sia vuoto per il backend, impostalo così context.Request.PathBase = PathString.Empty; - Log.Info($"Forwarding to base {destBase} with forwarded path {context.Request.Path} | original PathBase:{originalPathBase} | Path={originalPath})"); + Log.Debug($"Forwarding to base {destBase} with forwarded path {context.Request.Path} | original PathBase:{originalPathBase} | Path={originalPath})"); var requestOptions = new ForwarderRequestConfig { ActivityTimeout = TimeSpan.FromSeconds(100) }; var error = await _forwarder.SendAsync(context, destBase, _httpClientInvoker, requestOptions, _transformer, context.RequestAborted);