From c329437700e6b6007bb9681c2de0ccf6349718b1 Mon Sep 17 00:00:00 2001 From: Samuele Locatelli Date: Thu, 9 Apr 2026 07:10:14 +0200 Subject: [PATCH] Bozza detail upsert da testare --- .../Repository/Utils/StatsAggrRepository.cs | 19 ++-- .../Repository/Utils/StatsDetailRepository.cs | 105 ++++++++++++++++-- MP.IOC/Resources/ChangeLog.html | 2 +- MP.IOC/Resources/VersNum.txt | 2 +- MP.IOC/Resources/manifest.xml | 2 +- MP.IOC/Services/MetricsDbFlushService.cs | 2 +- 6 files changed, 112 insertions(+), 20 deletions(-) diff --git a/MP.Data/Repository/Utils/StatsAggrRepository.cs b/MP.Data/Repository/Utils/StatsAggrRepository.cs index 1f7b5cb2..a2ad622d 100644 --- a/MP.Data/Repository/Utils/StatsAggrRepository.cs +++ b/MP.Data/Repository/Utils/StatsAggrRepository.cs @@ -65,6 +65,14 @@ namespace MP.Data.Repository.Utils // 2. Se removeOld รจ true, manteniamo la logica originale (Eliminazione distruttiva) if (removeOld) { + // uso direttamente ExecuteDelete quando in EFCore8... +#if false + await dbCtx + .DbSetStatsAggr + .Where(x => x.Hour >= startDate && x.Hour <= endDate) + .ExecuteDeleteAsync(); +#endif + var itemsToRemove = await dbCtx.DbSetStatsAggr .Where(x => x.Hour >= minHour && x.Hour <= maxHour) .ToListAsync(); @@ -126,6 +134,7 @@ namespace MP.Data.Repository.Utils } } +#if false /// public async Task UpsertManyAsyncOrig(List listRecords, bool removeOld) { @@ -145,13 +154,6 @@ namespace MP.Data.Repository.Utils DateTime startDate = firstRec.Hour; DateTime endDate = lastRec.Hour; - // uso direttamente ExecuteDelete quando in EFCore8... -#if false - await dbCtx - .DbSetStatsAggr - .Where(x => x.Hour >= startDate && x.Hour <= endDate) - .ExecuteDeleteAsync(); -#endif var items = await dbCtx.DbSetStatsAggr .Where(x => x.Hour >= startDate && x.Hour <= endDate) .ToListAsync(); @@ -180,7 +182,8 @@ namespace MP.Data.Repository.Utils await tx.RollbackAsync(); throw; } - } + } +#endif #endregion Public Methods diff --git a/MP.Data/Repository/Utils/StatsDetailRepository.cs b/MP.Data/Repository/Utils/StatsDetailRepository.cs index ea3408c6..ba67a55a 100644 --- a/MP.Data/Repository/Utils/StatsDetailRepository.cs +++ b/MP.Data/Repository/Utils/StatsDetailRepository.cs @@ -68,8 +68,102 @@ namespace MP.Data.Repository.Utils return answ; } + /// public async Task UpsertManyAsync(List listRecords, bool removeOld) + { + if (listRecords == null || !listRecords.Any()) return 0; + + int answ = 0; + await using var dbCtx = await CreateContextAsync(); + await using var tx = await dbCtx.Database.BeginTransactionAsync(); + + try + { + // 1. Calcolo del range temporale basato sulla lista in arrivo per ottimizzare la query SQL + var minHour = listRecords.Min(x => x.Hour); + var maxHour = listRecords.Max(x => x.Hour); + + // 2. Gestione eliminazione distruttiva (se richiesto) + if (removeOld) + { + // uso direttamente ExecuteDelete quando in EFCore8... +#if false + await dbCtx + .DbSetStatsDet + .Where(x => x.Hour >= startDate && x.Hour <= endDate) + .ExecuteDeleteAsync(); +#endif + + var itemsToRemove = await dbCtx.DbSetStatsDet + .Where(x => x.Hour >= minHour && x.Hour <= maxHour) + .ToListAsync(); + + if (itemsToRemove.Any()) + { + dbCtx.DbSetStatsDet.RemoveRange(itemsToRemove); + await dbCtx.SaveChangesAsync(); + } + } + + // 3. LOGICA DI UPSERT (Merge basato su Destination + Type + Hour) + + // Recuperiamo dal DB solo i record che rientrano nel range temporale della lista in arrivo + var existingRecords = await dbCtx.DbSetStatsDet + .Where(x => x.Hour >= minHour && x.Hour <= maxHour) + .ToListAsync(); + + // Creiamo il dizionario di lookup con la chiave composta (Dest, Type, Hour) + var lookup = existingRecords.ToDictionary( + x => (x.Destination, x.Type, x.Hour), + x => x + ); + + foreach (var incoming in listRecords) + { + // Creiamo la chiave di ricerca basata sul record in arrivo + var key = (incoming.Destination, incoming.Type, incoming.Hour); + + if (lookup.TryGetValue(key, out var existing)) + { + // --- CASO: UPDATE --- + // Aggiorniamo i valori del record esistente con quelli nuovi + existing.RequestCount = incoming.RequestCount; + existing.AvgDuration = incoming.AvgDuration; + existing.MinDuration = incoming.MinDuration; + existing.MaxDuration = incoming.MaxDuration; + existing.NoReply = incoming.NoReply; + } + else + { + // --- CASO: INSERT --- + // Il record non esiste per questa combinazione, lo aggiungiamo + await dbCtx.DbSetStatsDet.AddAsync(incoming); + } + } + + // 4. Salvataggio finale delle modifiche (Insert + Update) + answ = await dbCtx.SaveChangesAsync(); + + // Commit della transazione + await tx.CommitAsync(); + + // Pulizia memoria del ChangeTracker per evitare accumuli durante sessioni lunghe + dbCtx.ChangeTracker.Clear(); + + return answ; + } + catch (Exception ex) + { + await tx.RollbackAsync(); + Log.Error(ex, "Error during StatsDetail UpsertManyAsync"); + throw; + } + } + +#if false + /// + public async Task UpsertManyAsyncOrig(List listRecords, bool removeOld) { int answ = 0; await using var dbCtx = await CreateContextAsync(); @@ -87,13 +181,7 @@ namespace MP.Data.Repository.Utils DateTime startDate = firstRec.Hour; DateTime endDate = lastRec.Hour; - // uso direttamente ExecuteDelete quando in EFCore8... -#if false - await dbCtx - .DbSetStatsDet - .Where(x => x.Hour >= startDate && x.Hour <= endDate) - .ExecuteDeleteAsync(); -#endif + var items = await dbCtx.DbSetStatsDet .Where(x => x.Hour >= startDate && x.Hour <= endDate) .ToListAsync(); @@ -121,7 +209,8 @@ namespace MP.Data.Repository.Utils await tx.RollbackAsync(); throw; } - } + } +#endif #endregion Public Methods diff --git a/MP.IOC/Resources/ChangeLog.html b/MP.IOC/Resources/ChangeLog.html index 699fcbc7..9120348f 100644 --- a/MP.IOC/Resources/ChangeLog.html +++ b/MP.IOC/Resources/ChangeLog.html @@ -1,6 +1,6 @@ Modulo MP-IOC -

Versione: 6.16.2604.819

+

Versione: 6.16.2604.907


Note di rilascio:
  • diff --git a/MP.IOC/Resources/VersNum.txt b/MP.IOC/Resources/VersNum.txt index d94d6cf4..3fb642e3 100644 --- a/MP.IOC/Resources/VersNum.txt +++ b/MP.IOC/Resources/VersNum.txt @@ -1 +1 @@ -6.16.2604.819 +6.16.2604.907 diff --git a/MP.IOC/Resources/manifest.xml b/MP.IOC/Resources/manifest.xml index 6576570a..13f343da 100644 --- a/MP.IOC/Resources/manifest.xml +++ b/MP.IOC/Resources/manifest.xml @@ -1,6 +1,6 @@ - 6.16.2604.819 + 6.16.2604.907 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 9e4eddaa..f5107234 100644 --- a/MP.IOC/Services/MetricsDbFlushService.cs +++ b/MP.IOC/Services/MetricsDbFlushService.cs @@ -290,7 +290,7 @@ namespace MP.IOC.Services { await using var scope = _scopeFactory.CreateAsyncScope(); var detailService = scope.ServiceProvider.GetRequiredService(); - await detailService.UpsertManyAsync(detailRecordsToInsert, true); + await detailService.UpsertManyAsync(detailRecordsToInsert, false); Log.Info($"[HOUR] Upserted {detailRecordsToInsert.Count} records to DB"); }