using EgwCoreLib.Utils; using Microsoft.EntityFrameworkCore; using MP.Data.DbModels.Utils; using NLog; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace MP.Data.Repository.Utils { public class StatsCodeRepository : BaseRepository, IStatsCodeRepository { #region Public Constructors public StatsCodeRepository(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 .DbSetStatusCode .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.DbSetStatusCode.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) { 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) { // uso direttamente ExecuteDelete quando in EFCore8... #if false await dbCtx .DbSetStatusCode .Where(x => x.Hour >= startDate && x.Hour <= endDate) .ExecuteDeleteAsync(); #endif var itemsToRemove = await dbCtx.DbSetStatusCode .Where(x => x.Hour >= minHour && x.Hour <= maxHour) .ToListAsync(); if (itemsToRemove.Any()) { dbCtx.DbSetStatusCode.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.DbSetStatusCode .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.Type, x.Hour, x.StatusCode), x => x ); foreach (var incoming in listRecords) { var key = (incoming.Destination, incoming.Type, incoming.Hour, incoming.StatusCode); if (lookup.TryGetValue(key, out var existing)) { // --- CASO: UPDATE --- existing.Count = incoming.Count; } else { // --- CASO: INSERT --- await dbCtx.DbSetStatusCode.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; } } #endregion Public Methods #region Protected Fields protected static NLog.Logger Log = LogManager.GetCurrentClassLogger(); #endregion Protected Fields } }