Completata dismissione LuxController!!!

This commit is contained in:
Samuele Locatelli
2026-03-21 12:14:35 +01:00
parent 4916dd42ff
commit ec58c870ea
26 changed files with 255 additions and 527 deletions
@@ -1,307 +0,0 @@
using EgwCoreLib.Lux.Data.DbModel.Production;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using NLog;
using StackExchange.Redis;
using System.Data;
namespace EgwCoreLib.Lux.Data.Controllers
{
internal class LuxController
{
#region Internal Methods
#if true
/// <summary>
/// Assegnazione in blocco degli item agli ODL corrispondenti
/// </summary>
/// <param name="dbList"></param>
/// <param name="dictParts"></param>
/// <returns></returns>
internal async Task<int> ProdItem2ODL_AssignAsync(List<ProductionODLModel> dbList, Dictionary<(int phaseId, int resId, string machine, int index), List<string>> dictParts)
{
int totalCreated = 0;
using (DataLayerContext dbCtx = new DataLayerContext())
{
// 1. Recuperiamo tutti i ProdBatchID coinvolti per fare una sola query
List<int> batchIds = dbList.Select(o => o.ProdBatchID).Distinct().ToList();
if (batchIds != null && batchIds.Count > 0)
{
// 2. Carichiamo in memoria i ProdItem necessari (solo ID e Tag per risparmiare RAM)
var itemsList = await dbCtx.DbSetProdItem
.Where(x => batchIds.Contains(x.ProdBatchID ?? 0) && x.ProdItemTag != null && x.ProdItemTag != "")
.Select(x => new { x.ProdItemID, x.ProdItemTag })
.ToListAsync();
// 1. Usiamo il "!" (null-forgiving operator) dopo x.ProdItemTag
// perché il filtro .Where sopra garantisce che non sia null.
var itemLookup = itemsList
.GroupBy(x => x.ProdItemTag!)
.ToDictionary(
g => g.Key,
g => g.First().ProdItemID,
StringComparer.OrdinalIgnoreCase
);
using var transaction = await dbCtx.Database.BeginTransactionAsync();
try
{
var relationsToInsert = new List<ProductionItem2ODLModel>();
foreach (var odl in dbList)
{
var key = (odl.PhaseID ?? 0, odl.ResourceID ?? 0, odl.ProdPlantCod, odl.Index);
if (dictParts.TryGetValue(key, out List<string> tagList))
{
foreach (var tag in tagList)
{
// 3. Cerchiamo l'ID corrispondente al tag nel nostro lookup locale
if (itemLookup.TryGetValue(tag, out int realItemId))
{
relationsToInsert.Add(new ProductionItem2ODLModel
{
ProdODLID = odl.ProdODLID,
ProdItemID = realItemId,
DtAssign = DateTime.Now
});
}
else
{
//Log.Warning($"Tag {tag} non trovato nel database per i batch selezionati.");
}
}
}
}
if (relationsToInsert.Any())
{
await dbCtx.DbSetProdItem2ODL.AddRangeAsync(relationsToInsert);
totalCreated = relationsToInsert.Count;
await dbCtx.SaveChangesAsync();
}
await transaction.CommitAsync();
}
catch (Exception exc)
{
await transaction.RollbackAsync();
Log.Error($"Errore nel salvataggio relazioni ODL-Parts: {exc.Message}");
throw;
}
}
}
return totalCreated;
}
#endif
#if false
/// <summary>
/// Elenco da DB delle stats aggregate dato periodo inizio/fine
/// </summary>
/// <param name="dtStart"></param>
/// <param name="dtEnd"></param>
/// <returns></returns>
internal async Task<List<StatsAggregatedModel>> StatsAggrGetAsync(DateTime dtStart, DateTime dtEnd)
{
List<StatsAggregatedModel> answ = new List<StatsAggregatedModel>();
//using (DataLayerContext dbCtx = new DataLayerContext(_config))
using (DataLayerContext dbCtx = new DataLayerContext())
{
// recupero ed ordino per data-ora
answ = await dbCtx
.DbSetStatsAggr
.Where(x => x.Hour >= dtStart && x.Hour <= dtEnd)
.AsNoTracking()
.OrderBy(x => x.Hour)
.ToListAsync();
}
return answ;
}
/// <summary>
/// Range periodo per chiamate aggregate
/// </summary>
/// <returns></returns>
internal async Task<Utils.DtUtils.Periodo> StatsAggrRangeAsync()
{
Utils.DtUtils.Periodo answ = new Utils.DtUtils.Periodo(Utils.DtUtils.PeriodSet.Today);
using (DataLayerContext dbCtx = new DataLayerContext())
{
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;
return answ;
}
}
/// <summary>
/// Esegue insert statistiche aggregate sul DB
/// </summary>
/// <param name="listRecords">Elenco dei record da inserire</param>
/// <param name="removeOld">Se true preventivamente elimina record nel periodo richiesto</param>
/// <returns></returns>
internal async Task<long> StatsAggrUpsertAsync(List<StatsAggregatedModel> listRecords, bool removeOld)
{
int answ = 0;
//using (DataLayerContext dbCtx = new DataLayerContext(_config))
using (DataLayerContext dbCtx = new DataLayerContext())
{
// 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();
// libero memoria del changeTracker
dbCtx.ChangeTracker.Clear();
}
return answ;
}
#endif
#if false
/// <summary>
/// Recupera dati stats di dettaglio dato filtro envir/tipo (opzionali) e periodo
/// </summary>
/// <param name="dtStart"></param>
/// <param name="dtEnd"></param>
/// <param name="sEnvir"></param>
/// <param name="sType"></param>
/// <returns></returns>
internal async Task<List<StatsDetailModel>> StatsDetailModelGetAsync(DateTime dtStart, DateTime dtEnd, string sEnvir = "", string sType = "")
{
List<StatsDetailModel> answ = new List<StatsDetailModel>();
//using (DataLayerContext dbCtx = new DataLayerContext(_config))
using (DataLayerContext dbCtx = new DataLayerContext())
{
// 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;
}
/// <summary>
/// Range periodo x chiamate detail eventualmente filtrate
/// </summary>
/// <param name="sEnvir"></param>
/// <param name="sType"></param>
/// <returns></returns>
internal async Task<Utils.DtUtils.Periodo> StatsDetailModelRangeAsync(string sEnvir, string sType)
{
Utils.DtUtils.Periodo answ = new Utils.DtUtils.Periodo(Utils.DtUtils.PeriodSet.Today);
using (DataLayerContext dbCtx = new DataLayerContext())
{
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;
}
}
/// <summary>
/// Esegue insert statistiche di dettaglio sul DB
/// </summary>
/// <param name="listRecords">Elenco dei record da inserire</param>
/// <param name="removeOld">Se true preventivamente elimina record nel periodo richiesto</param>
/// <returns></returns>
internal async Task<long> StatsDetailModelUpsertAsync(List<StatsDetailModel> listRecords, bool removeOld)
{
int answ = 0;
//using (DataLayerContext dbCtx = new DataLayerContext(_config))
using (DataLayerContext dbCtx = new DataLayerContext())
{
// 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();
// libero memoria del changeTracker
dbCtx.ChangeTracker.Clear();
}
return answ;
}
#endif
#endregion Internal Methods
#region Private Fields
private static IConfiguration _configuration;
private static Logger Log = LogManager.GetCurrentClassLogger();
#endregion Private Fields
}
}
@@ -34,7 +34,7 @@ namespace EgwCoreLib.Lux.Data.Repository.Utils
await using var dbCtx = await CreateContextAsync();
// Wrap in transaction for atomicity
await using var tx = dbCtx.Database.BeginTransaction();
await using var tx = await dbCtx.Database.BeginTransactionAsync();
try
{
DateTime now = DateTime.Now;
@@ -163,7 +163,7 @@ namespace EgwCoreLib.Lux.Data.Repository.Utils
await using var dbCtx = await CreateContextAsync();
// Wrap in transaction for atomicity when saving multiple rows
await using var tx = dbCtx.Database.BeginTransaction();
await using var tx = await dbCtx.Database.BeginTransactionAsync();
try
{
foreach (var row in rows)
@@ -32,7 +32,7 @@ namespace EgwCoreLib.Lux.Data.Repository.Utils
await using var dbCtx = await CreateContextAsync();
// Wrap in transaction for atomicity
await using var tx = dbCtx.Database.BeginTransaction();
await using var tx = await dbCtx.Database.BeginTransactionAsync();
try
{
var currRec = await dbCtx.DbSetTemplateRow
@@ -136,7 +136,7 @@ namespace EgwCoreLib.Lux.Data.Repository.Utils
await using var dbCtx = await CreateContextAsync();
// Wrap in transaction for atomicity when saving multiple rows
await using var tx = dbCtx.Database.BeginTransaction();
await using var tx = await dbCtx.Database.BeginTransactionAsync();
try
{
foreach (var row in rows)
@@ -30,7 +30,7 @@ namespace EgwCoreLib.Lux.Data.Repository.Job
await using var dbCtx = await CreateContextAsync();
// Wrap in transaction for atomicity (multi-row update + delete)
await using var tx = dbCtx.Database.BeginTransaction();
await using var tx = await dbCtx.Database.BeginTransactionAsync();
try
{
var dbResult = await dbCtx.DbSetJobStep
@@ -93,7 +93,7 @@ namespace EgwCoreLib.Lux.Data.Repository.Job
await using var dbCtx = await CreateContextAsync();
// Wrap in transaction for atomicity (multi-row update)
await using var tx = dbCtx.Database.BeginTransaction();
await using var tx = await dbCtx.Database.BeginTransactionAsync();
try
{
var currRec = await dbCtx.DbSetJobStep
@@ -30,7 +30,7 @@ namespace EgwCoreLib.Lux.Data.Repository.Job
await using var dbCtx = await CreateContextAsync();
// Wrap in transaction for atomicity (multi-row update + delete)
await using var tx = dbCtx.Database.BeginTransaction();
await using var tx = await dbCtx.Database.BeginTransactionAsync();
try
{
var dbResult = await dbCtx.DbSetJobTask
@@ -86,7 +86,7 @@ namespace EgwCoreLib.Lux.Data.Repository.Job
await using var dbCtx = await CreateContextAsync();
// Wrap in transaction for atomicity (multi-row add + multi-row remove)
await using var tx = dbCtx.Database.BeginTransaction();
await using var tx = await dbCtx.Database.BeginTransactionAsync();
try
{
var currRec = await dbCtx.DbSetJobTask
@@ -144,7 +144,7 @@ namespace EgwCoreLib.Lux.Data.Repository.Job
await using var dbCtx = await CreateContextAsync();
// Wrap in transaction for atomicity (multi-row update)
await using var tx = dbCtx.Database.BeginTransaction();
await using var tx = await dbCtx.Database.BeginTransactionAsync();
try
{
var currRec = await dbCtx.DbSetJobTask
@@ -14,6 +14,8 @@ namespace EgwCoreLib.Lux.Data.Repository.Production
/// <returns></returns>
Task<bool> AddAsync(ProductionODLModel entity);
Task<int> AssignProdItem2OdlAsync(List<ProductionODLModel> dbList, Dictionary<(int phaseId, int resId, string machine, int index), List<string>> dictParts);
/// <summary>
/// Insert sul DB di un elenco ODL con calcolo della relativa KEY a cui poter, successivamente, collegare i record child (items)
/// </summary>
@@ -65,7 +65,7 @@ namespace EgwCoreLib.Lux.Data.Repository.Production
}
await using var dbCtx = await CreateContextAsync();
await using var tx = dbCtx.Database.BeginTransaction();
await using var tx = await dbCtx.Database.BeginTransactionAsync();
try
{
// Tentativo di deserializzazione
@@ -25,7 +25,7 @@ namespace EgwCoreLib.Lux.Data.Repository.Production
{
await using var dbCtx = await CreateContextAsync();
int totUpd = 0;
await using var tx = dbCtx.Database.BeginTransaction();
await using var tx = await dbCtx.Database.BeginTransactionAsync();
try
{
int rowsAffected = await dbCtx.DbSetProdItem
@@ -56,7 +56,7 @@ namespace EgwCoreLib.Lux.Data.Repository.Production
{
await using var dbCtx = await CreateContextAsync();
int totUpd = 0;
await using var tx = dbCtx.Database.BeginTransaction();
await using var tx = await dbCtx.Database.BeginTransactionAsync();
try
{
foreach (var entry in itemsToAssign)
@@ -93,7 +93,7 @@ namespace EgwCoreLib.Lux.Data.Repository.Production
{
await using var dbCtx = await CreateContextAsync();
int numItem = 0;
await using var tx = dbCtx.Database.BeginTransaction();
await using var tx = await dbCtx.Database.BeginTransactionAsync();
try
{
numItem = await dbCtx.DbSetProdItem
@@ -24,6 +24,83 @@ namespace EgwCoreLib.Lux.Data.Repository.Production
return await dbCtx.SaveChangesAsync() > 0;
}
/// <summary>
/// Assegnazione in blocco degli item agli ODL corrispondenti
/// </summary>
/// <param name="dbList"></param>
/// <param name="dictParts"></param>
/// <returns></returns>
public async Task<int> AssignProdItem2OdlAsync(List<ProductionODLModel> dbList, Dictionary<(int phaseId, int resId, string machine, int index), List<string>> dictParts)
{
int totalCreated = 0;
await using var dbCtx = await CreateContextAsync();
// 1. Recuperiamo tutti i ProdBatchID coinvolti per fare una sola query
List<int> batchIds = dbList.Select(o => o.ProdBatchID).Distinct().ToList();
if (batchIds != null && batchIds.Count > 0)
{
// 2. Carichiamo in memoria i ProdItem necessari (solo ID e Tag per risparmiare RAM)
var itemsList = await dbCtx.DbSetProdItem
.Where(x => batchIds.Contains(x.ProdBatchID ?? 0) && x.ProdItemTag != null && x.ProdItemTag != "")
.Select(x => new { x.ProdItemID, x.ProdItemTag })
.ToListAsync();
// 1. Usiamo il "!" (null-forgiving operator) dopo x.ProdItemTag
// perché il filtro .Where sopra garantisce che non sia null.
var itemLookup = itemsList
.GroupBy(x => x.ProdItemTag!)
.ToDictionary(
g => g.Key,
g => g.First().ProdItemID,
StringComparer.OrdinalIgnoreCase
);
await using var tx = await dbCtx.Database.BeginTransactionAsync();
try
{
var relationsToInsert = new List<ProductionItem2ODLModel>();
foreach (var odl in dbList)
{
var key = (odl.PhaseID ?? 0, odl.ResourceID ?? 0, odl.ProdPlantCod, odl.Index);
if (dictParts.TryGetValue(key, out List<string> tagList))
{
foreach (var tag in tagList)
{
// 3. Cerchiamo l'ID corrispondente al tag nel nostro lookup locale
if (itemLookup.TryGetValue(tag, out int realItemId))
{
relationsToInsert.Add(new ProductionItem2ODLModel
{
ProdODLID = odl.ProdODLID,
ProdItemID = realItemId,
DtAssign = DateTime.Now
});
}
}
}
}
if (relationsToInsert.Any())
{
await dbCtx.DbSetProdItem2ODL.AddRangeAsync(relationsToInsert);
totalCreated = relationsToInsert.Count;
await dbCtx.SaveChangesAsync();
}
await tx.CommitAsync();
}
catch
{
await tx.RollbackAsync();
throw;
}
}
return totalCreated;
}
/// <summary>
/// Insert sul DB di un elenco ODL con calcolo della relativa KEY a cui poter, successivamente, collegare i record child (items)
/// </summary>
@@ -35,8 +112,7 @@ namespace EgwCoreLib.Lux.Data.Repository.Production
await using var dbCtx = await CreateContextAsync();
// avvio transazione
using var transaction = await dbCtx.Database.BeginTransactionAsync();
await using var tx = await dbCtx.Database.BeginTransactionAsync();
try
{
int cYear = DateTime.Today.Year;
@@ -55,16 +131,16 @@ namespace EgwCoreLib.Lux.Data.Repository.Production
await dbCtx.Database.ExecuteSqlRawAsync("CALL stp_ProdOdl_UpdateTag(@pProdBatchID, @pPrefix, @pYear)", pProdBatchID, pPref, pYear);
// 4. Conferma transazione
await transaction.CommitAsync();
await tx.CommitAsync();
// A questo punto, ogni oggetto in 'listOdl2ins' ha il ProdODLID aggiornato dal DB
return listOdl2ins;
}
catch (Exception ex)
catch
{
await transaction.RollbackAsync();
await tx.RollbackAsync();
// Logga l'errore secondo le tue necessità
throw new Exception("Errore durante la creazione massiva degli ODL", ex);
throw;
}
}
@@ -65,7 +65,7 @@ namespace EgwCoreLib.Lux.Data.Repository.Sales
await using var dbCtx = await CreateContextAsync();
// Wrap in transaction for atomicity (parent + children clone)
await using var tx = dbCtx.Database.BeginTransaction();
await using var tx = await dbCtx.Database.BeginTransactionAsync();
try
{
DateTime now = DateTime.Now;
@@ -224,7 +224,7 @@ namespace EgwCoreLib.Lux.Data.Repository.Sales
await using var dbCtx = await CreateContextAsync();
// Wrap in transaction for atomicity (batch update multiple rows)
await using var tx = dbCtx.Database.BeginTransaction();
await using var tx = await dbCtx.Database.BeginTransactionAsync();
try
{
foreach (var row in rows)
@@ -29,7 +29,7 @@ namespace EgwCoreLib.Lux.Data.Repository.Sales
await using var dbCtx = await CreateContextAsync();
// Wrap in transaction for atomicity (multi-row update + delete)
await using var tx = dbCtx.Database.BeginTransaction();
await using var tx = await dbCtx.Database.BeginTransactionAsync();
try
{
// 1. Recupero il record da eliminare
@@ -116,7 +116,7 @@ namespace EgwCoreLib.Lux.Data.Repository.Sales
await using var dbCtx = await CreateContextAsync();
// Wrap in transaction for atomicity (batch update multiple rows)
await using var tx = dbCtx.Database.BeginTransaction();
await using var tx = await dbCtx.Database.BeginTransactionAsync();
try
{
foreach (var row in rows)
@@ -38,7 +38,7 @@ namespace EgwCoreLib.Lux.Data.Repository.Sales
OrderModel? newRec = null;
await using var dbCtx = await CreateContextAsync();
// avvio transazione
using var transaction = await dbCtx.Database.BeginTransactionAsync();
await using var tx = await dbCtx.Database.BeginTransactionAsync();
try
{
DateTime now = DateTime.Now;
@@ -168,13 +168,12 @@ namespace EgwCoreLib.Lux.Data.Repository.Sales
await dbCtx.Database.ExecuteSqlRawAsync("CALL stp_ProdItem_UpdateProdItemTag(0,0);");
// committo in un unica transazione (da provare!!!)
await transaction.CommitAsync();
await tx.CommitAsync();
}
}
catch
{
await transaction.RollbackAsync();
// sollevo exception
await tx.RollbackAsync();
throw;
}
return newRec;
@@ -246,17 +245,17 @@ namespace EgwCoreLib.Lux.Data.Repository.Sales
await using var dbCtx = await CreateContextAsync();
// Wrap in transaction for atomicity (batch update multiple rows)
await using var tx = dbCtx.Database.BeginTransaction();
await using var tx = await dbCtx.Database.BeginTransactionAsync();
try
{
foreach (var row in rows)
dbCtx.Entry(row).State = EntityState.Modified;
bool done = await dbCtx.SaveChangesAsync() > 0;
if (done)
tx.Commit();
return done;
}
catch
@@ -33,7 +33,7 @@ namespace EgwCoreLib.Lux.Data.Repository.Sales
await using var dbCtx = await CreateContextAsync();
// Wrap in transaction for atomicity (multi-row update + delete)
await using var tx = dbCtx.Database.BeginTransaction();
await using var tx = await dbCtx.Database.BeginTransactionAsync();
try
{
// 1. Recupero il record da eliminare
@@ -137,7 +137,7 @@ namespace EgwCoreLib.Lux.Data.Repository.Sales
await using var dbCtx = await CreateContextAsync();
// Wrap in transaction for atomicity (batch update multiple rows)
await using var tx = dbCtx.Database.BeginTransaction();
await using var tx = await dbCtx.Database.BeginTransactionAsync();
try
{
foreach (var row in rows)
@@ -183,7 +183,7 @@ namespace EgwCoreLib.Lux.Data.Repository.Sales
await using var dbCtx = await CreateContextAsync();
// Wrap in transaction for atomicity (batch update multiple rows)
await using var tx = dbCtx.Database.BeginTransaction();
await using var tx = await dbCtx.Database.BeginTransactionAsync();
try
{
// recupero offerta...
@@ -357,7 +357,7 @@ namespace EgwCoreLib.Lux.Data.Repository.Sales
// context
await using var dbCtx = await CreateContextAsync();
// Wrap in transaction for atomicity (batch update multiple rows)
await using var tx = dbCtx.Database.BeginTransaction();
await using var tx = await dbCtx.Database.BeginTransactionAsync();
try
{
// verifica preliminare: serve SSE stato e estimate non corrispondono...
@@ -60,7 +60,7 @@ namespace EgwCoreLib.Lux.Data.Repository.Stats
{
int answ = 0;
await using var dbCtx = await CreateContextAsync();
await using var tx = dbCtx.Database.BeginTransaction();
await using var tx = await dbCtx.Database.BeginTransactionAsync();
try
{
// in primis se richiesto calcolo range periodo e svuoto...
@@ -85,7 +85,7 @@ namespace EgwCoreLib.Lux.Data.Repository.Stats
{
int answ = 0;
await using var dbCtx = await CreateContextAsync();
await using var tx = dbCtx.Database.BeginTransaction();
await using var tx = await dbCtx.Database.BeginTransactionAsync();
try
{
// in primis se richiesto calcolo range periodo e svuoto...
@@ -30,7 +30,7 @@ namespace EgwCoreLib.Lux.Data.Repository.Utils
await using var dbCtx = await CreateContextAsync();
// Wrap in transaction for atomicity (multi-row update + delete)
await using var tx = dbCtx.Database.BeginTransaction();
await using var tx = await dbCtx.Database.BeginTransactionAsync();
try
{
// 1. Recupero il record da eliminare
@@ -92,7 +92,7 @@ namespace EgwCoreLib.Lux.Data.Repository.Utils
await using var dbCtx = await CreateContextAsync();
// Wrap in transaction for atomicity (multi-row update - swap positions)
await using var tx = dbCtx.Database.BeginTransaction();
await using var tx = await dbCtx.Database.BeginTransactionAsync();
try
{
// 1. Recupero il record corrente
+109 -133
View File
@@ -1,13 +1,10 @@
using EgwCoreLib.Lux.Core.Stats;
using EgwCoreLib.Lux.Data.Controllers;
using EgwCoreLib.Lux.Data.DbModel.Stats;
using EgwCoreLib.Lux.Data.Services.Stats;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using NLog;
using StackExchange.Redis;
using System.Text;
using static Egw.Window.Data.Enums;
namespace EgwCoreLib.Lux.Data.Services
@@ -23,7 +20,6 @@ namespace EgwCoreLib.Lux.Data.Services
IConfiguration configuration,
IConnectionMultiplexer redisConn,
IServiceProvider serviceProvider) : base(configuration, redisConn)
//public CalcRuidService(IConfiguration configuration, IConnectionMultiplexer redisConn, string redisBaseKey, TimeSpan retention, TimeSpan archivePeriod) : base(configuration, redisConn)
{
// leggo conf retention/archive da config...
string cleanupDayTTL = configuration.GetValue<string>("ServerConf:CleanupDayTTL") ?? "360";
@@ -38,28 +34,11 @@ namespace EgwCoreLib.Lux.Data.Services
_base = rBaseKey.TrimEnd(':');
_retention = TimeSpan.FromDays(dayTTL);
_archivePeriod = TimeSpan.FromDays(archTTL);
// conf DB
string connStr = _config.GetConnectionString("Lux.All") ?? "";
if (string.IsNullOrEmpty(connStr))
{
Log.Error("ConnString empty!");
}
else
{
//dbController = new Controllers.LuxController(_config);
dbController = new LuxController();
StringBuilder sb = new StringBuilder();
sb.AppendLine($"CalcRuidService | LuxController OK");
Log.Info(sb.ToString());
}
Log.Info($"CalcRuidService | Started");
}
#endregion Public Constructors
public IStatsAggrService StAggServ => _serviceProvider.GetRequiredService<IStatsAggrService>();
public IStatsDetailService StDetServ => _serviceProvider.GetRequiredService<IStatsDetailService>();
private readonly IServiceProvider _serviceProvider;
#region Public Enums
/// <summary>
@@ -82,6 +61,13 @@ namespace EgwCoreLib.Lux.Data.Services
#endregion Public Enums
#region Public Properties
public IStatsAggrService StAggServ => _serviceProvider.GetRequiredService<IStatsAggrService>();
public IStatsDetailService StDetServ => _serviceProvider.GetRequiredService<IStatsDetailService>();
#endregion Public Properties
#region Public Methods
/// <summary>
@@ -138,109 +124,6 @@ namespace EgwCoreLib.Lux.Data.Services
return ruid;
}
#if false
/// <summary>
/// Metodo di Cleanup periodico
/// </summary>
/// <param name="environment">Environment calcolo</param>
/// <param name="tipo">Tipologia richiesta</param>
/// <returns></returns>
public async Task CleanupOldRequestsAsync(string environment, string tipo)
{
var cutoff = DateTimeOffset.Now.Add(-_retention).ToUnixTimeMilliseconds();
var setKey = GetSortedSetKey(environment, tipo);
var oldIds = await _db.SortedSetRangeByScoreAsync(setKey, stop: cutoff);
if (oldIds.Length == 0) return;
var batch = _db.CreateBatch();
var tasks = new List<Task>();
foreach (var id in oldIds)
{
var ruid = id.ToString();
var hashKey = GetRequestKey(ruid);
var uid = await _db.HashGetAsync(hashKey, "UID");
if (!uid.IsNull)
{
var uidKey = GetUidSetKey(uid);
tasks.Add(batch.SetRemoveAsync(uidKey, ruid));
tasks.Add(batch.SetLengthAsync(uidKey).ContinueWith(t =>
{
if (t.Result == 0)
_db.KeyDelete(uidKey);
}));
}
tasks.Add(batch.KeyDeleteAsync(hashKey));
}
tasks.Add(batch.SortedSetRemoveRangeByScoreAsync(setKey, double.NegativeInfinity, cutoff));
tasks.Add(batch.SortedSetLengthAsync(setKey).ContinueWith(t =>
{
if (t.Result == 0)
_db.KeyDelete(setKey);
}));
batch.Execute();
await Task.WhenAll(tasks);
}
/// <summary>
/// Metodo di Cleanup dato periodo limite prima del quale va eliminato l'elenco dei dati
/// </summary>
/// <param name="environment">Environment calcolo</param>
/// <param name="tipo">Tipologia richiesta</param>
/// <param name="timeLimit">Limite temporale dati da eliminare</param>
/// <returns></returns>
public async Task CleanupOldRequestsAsync(string environment, string tipo, TimeSpan timeLimit)
{
var cutoff = DateTimeOffset.Now.Add(-timeLimit).ToUnixTimeMilliseconds();
var setKey = GetSortedSetKey(environment, tipo);
var oldIds = await _db.SortedSetRangeByScoreAsync(setKey, stop: cutoff);
if (oldIds.Length == 0) return;
var batch = _db.CreateBatch();
var tasks = new List<Task>();
foreach (var id in oldIds)
{
var ruid = id.ToString();
var hashKey = GetRequestKey(ruid);
var uid = await _db.HashGetAsync(hashKey, "UID");
if (!uid.IsNull)
{
var uidKey = GetUidSetKey(uid);
tasks.Add(batch.SetRemoveAsync(uidKey, ruid));
tasks.Add(batch.SetLengthAsync(uidKey).ContinueWith(t =>
{
if (t.Result == 0)
_db.KeyDelete(uidKey);
}));
}
tasks.Add(batch.KeyDeleteAsync(hashKey));
}
tasks.Add(batch.SortedSetRemoveRangeByScoreAsync(setKey, double.NegativeInfinity, cutoff));
tasks.Add(batch.SortedSetLengthAsync(setKey).ContinueWith(t =>
{
if (t.Result == 0)
_db.KeyDelete(setKey);
}));
batch.Execute();
await Task.WhenAll(tasks);
}
#endif
/// <summary>
/// Effettua pulizia delle statistiche collezionate una volta migrate sul DB
/// </summary>
@@ -845,24 +728,117 @@ namespace EgwCoreLib.Lux.Data.Services
#region Private Fields
private static Logger Log = LogManager.GetCurrentClassLogger();
private readonly TimeSpan _archivePeriod;
private readonly string _base;
private readonly IDatabase _db;
private readonly TimeSpan _retention;
private readonly Random _rnd = new Random();
private readonly IServiceProvider _serviceProvider;
#endregion Private Fields
#region Private Properties
#if false
/// <summary>
/// Metodo di Cleanup periodico
/// </summary>
/// <param name="environment">Environment calcolo</param>
/// <param name="tipo">Tipologia richiesta</param>
/// <returns></returns>
public async Task CleanupOldRequestsAsync(string environment, string tipo)
{
var cutoff = DateTimeOffset.Now.Add(-_retention).ToUnixTimeMilliseconds();
var setKey = GetSortedSetKey(environment, tipo);
private static LuxController dbController { get; set; } = null!;
var oldIds = await _db.SortedSetRangeByScoreAsync(setKey, stop: cutoff);
if (oldIds.Length == 0) return;
#endregion Private Properties
var batch = _db.CreateBatch();
var tasks = new List<Task>();
foreach (var id in oldIds)
{
var ruid = id.ToString();
var hashKey = GetRequestKey(ruid);
var uid = await _db.HashGetAsync(hashKey, "UID");
if (!uid.IsNull)
{
var uidKey = GetUidSetKey(uid);
tasks.Add(batch.SetRemoveAsync(uidKey, ruid));
tasks.Add(batch.SetLengthAsync(uidKey).ContinueWith(t =>
{
if (t.Result == 0)
_db.KeyDelete(uidKey);
}));
}
tasks.Add(batch.KeyDeleteAsync(hashKey));
}
tasks.Add(batch.SortedSetRemoveRangeByScoreAsync(setKey, double.NegativeInfinity, cutoff));
tasks.Add(batch.SortedSetLengthAsync(setKey).ContinueWith(t =>
{
if (t.Result == 0)
_db.KeyDelete(setKey);
}));
batch.Execute();
await Task.WhenAll(tasks);
}
/// <summary>
/// Metodo di Cleanup dato periodo limite prima del quale va eliminato l'elenco dei dati
/// </summary>
/// <param name="environment">Environment calcolo</param>
/// <param name="tipo">Tipologia richiesta</param>
/// <param name="timeLimit">Limite temporale dati da eliminare</param>
/// <returns></returns>
public async Task CleanupOldRequestsAsync(string environment, string tipo, TimeSpan timeLimit)
{
var cutoff = DateTimeOffset.Now.Add(-timeLimit).ToUnixTimeMilliseconds();
var setKey = GetSortedSetKey(environment, tipo);
var oldIds = await _db.SortedSetRangeByScoreAsync(setKey, stop: cutoff);
if (oldIds.Length == 0) return;
var batch = _db.CreateBatch();
var tasks = new List<Task>();
foreach (var id in oldIds)
{
var ruid = id.ToString();
var hashKey = GetRequestKey(ruid);
var uid = await _db.HashGetAsync(hashKey, "UID");
if (!uid.IsNull)
{
var uidKey = GetUidSetKey(uid);
tasks.Add(batch.SetRemoveAsync(uidKey, ruid));
tasks.Add(batch.SetLengthAsync(uidKey).ContinueWith(t =>
{
if (t.Result == 0)
_db.KeyDelete(uidKey);
}));
}
tasks.Add(batch.KeyDeleteAsync(hashKey));
}
tasks.Add(batch.SortedSetRemoveRangeByScoreAsync(setKey, double.NegativeInfinity, cutoff));
tasks.Add(batch.SortedSetLengthAsync(setKey).ContinueWith(t =>
{
if (t.Result == 0)
_db.KeyDelete(setKey);
}));
batch.Execute();
await Task.WhenAll(tasks);
}
#endif
#region Private Methods
@@ -1,7 +1,6 @@
using EgwCoreLib.Lux.Core.Generic;
using EgwCoreLib.Lux.Core.RestPayload;
using EgwCoreLib.Lux.Data.Controllers;
using EgwCoreLib.Lux.Data.DbModel.Production;
using EgwCoreLib.Lux.Data.DbModel.Sales;
using EgwCoreLib.Lux.Data.Services.Config;
using EgwCoreLib.Lux.Data.Services.Items;
@@ -15,7 +14,6 @@ using NLog;
using StackExchange.Redis;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Text;
namespace EgwCoreLib.Lux.Data.Services
{
@@ -29,23 +27,8 @@ namespace EgwCoreLib.Lux.Data.Services
IServiceProvider serviceProvider) : base(configuration, RedisConn)
{
_serviceProvider = serviceProvider;
// conf DB
string connStr = _config.GetConnectionString("Lux.All") ?? "";
if (string.IsNullOrEmpty(connStr))
{
Log.Error("ConnString empty!");
}
else
{
StringBuilder sb = new StringBuilder();
//dbController = new Controllers.LuxController(_config);
dbController = new LuxController();
sb.AppendLine($"LuxController OK");
dataSimController = new DataSimulatorController();
sb.AppendLine($"DataSimController OK");
sb.AppendLine($"LuxController OK");
Log.Info(sb.ToString());
}
dataSimController = new DataSimulatorController();
Log.Info($"DataSimController OK");
}
#endregion Public Constructors
@@ -128,29 +111,6 @@ namespace EgwCoreLib.Lux.Data.Services
return result;
}
/// <summary>
/// Assegnazione in blocco degli item agli ODL corrispondenti
/// </summary>
/// <param name="dbList"></param>
/// <param name="dictParts"></param>
/// <returns></returns>
public async Task<int> ProdItem2ODL_AssignAsync(List<ProductionODLModel> dbList, Dictionary<(int phaseId, int resId, string machine, int index), List<string>> dictParts)
{
using var activity = StartActivity();
string source = "DB+REDIS";
// calcolo
int fatto = await dbController.ProdItem2ODL_AssignAsync(dbList, dictParts);
// svuoto cache...
//await ExecFlushRedisPatternAsync((RedisValue)$"{_redisBaseKey}:ProdItems:*");
await ExecFlushRedisPatternAsync((RedisValue)$"{redisBaseKey}:ProdItems:OrdRowId:*");
await ExecFlushRedisPatternAsync((RedisValue)$"{redisBaseKey}:ProdGroup:OrdRowId:*");
await ExecFlushRedisPatternAsync((RedisValue)$"{redisBaseKey}:OrderRows:*");
await ExecFlushRedisPatternAsync((RedisValue)$"{redisBaseKey}:OrderRowsByState:*");
activity?.SetTag("data.source", source);
LogTrace($"{source} | trace: {activity?.TraceId} | {activity?.Duration.TotalMilliseconds}ms");
return fatto;
}
/// <summary>
/// Esegue salvataggio BOM sul DB
/// </summary>
@@ -502,7 +462,6 @@ namespace EgwCoreLib.Lux.Data.Services
#region Private Properties
private static DataSimulatorController dataSimController { get; set; } = null!;
private static LuxController dbController { get; set; } = null!;
#endregion Private Properties
}
@@ -7,6 +7,8 @@ namespace EgwCoreLib.Lux.Data.Services.Production
{
#region Public Methods
Task<int> AssignProdItem2OdlAsync(List<ProductionODLModel> dbList, Dictionary<(int phaseId, int resId, string machine, int index), List<string>> dictParts);
/// <summary>
/// Insert sul DB di un elenco ODL con calcolo della relativa KEY a cui poter, successivamente, collegare i record child (items)
/// </summary>
@@ -23,6 +23,27 @@ namespace EgwCoreLib.Lux.Data.Services.Production
#region Public Methods
/// <summary>
/// Assegnazione in blocco degli item agli ODL corrispondenti
/// </summary>
/// <param name="dbList"></param>
/// <param name="dictParts"></param>
/// <returns></returns>
public async Task<int> AssignProdItem2OdlAsync(List<ProductionODLModel> dbList, Dictionary<(int phaseId, int resId, string machine, int index), List<string>> dictParts)
{
return await TraceAsync($"{_className}.Upsert", async (activity) =>
{
string operation = "AssignProdItem2Odl";
var result = await _repo.AssignProdItem2OdlAsync(dbList, dictParts);
activity?.SetTag("db.operation", operation);
await ClearCacheAsync($"{_redisBaseKey}:{_className}");
return result;
});
}
/// <summary>
/// Insert sul DB di un elenco ODL con calcolo della relativa KEY a cui poter, successivamente, collegare i record child (items)
/// </summary>
+1 -1
View File
@@ -4,7 +4,7 @@
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<Version>1.1.2603.2111</Version>
<Version>1.1.2603.2112</Version>
</PropertyGroup>
<ItemGroup>
@@ -265,7 +265,7 @@ namespace Lux.UI.Components.Pages
}
// ora lavoro sugli items x collegarli agli ODL... parto dal dizionario e cerco nell'elenco degli ODL creati...
int fatto = await DLService.ProdItem2ODL_AssignAsync(prodODL_DbList, dictParts);
int fatto = await POService.AssignProdItem2OdlAsync(prodODL_DbList, dictParts);
// aggiorno sales order status a ProdOdl creato
foreach (var ordRowId in ordRowIdList)
+1 -1
View File
@@ -5,7 +5,7 @@
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UserSecretsId>aspnet-Lux.UI-a758c101-a2f4-4e38-977d-1c4887dbbd50</UserSecretsId>
<Version>1.1.2603.2111</Version>
<Version>1.1.2603.2112</Version>
</PropertyGroup>
<ItemGroup>
+1 -1
View File
@@ -1,6 +1,6 @@
<body>
<i>LUX - Web Windows MES</i>
<h4>Versione: 1.1.2603.2111</h4>
<h4>Versione: 1.1.2603.2112</h4>
<br /> Note di rilascio:
<ul>
<li>
+1 -1
View File
@@ -1 +1 @@
1.1.2603.2111
1.1.2603.2112
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<item>
<version>1.1.2603.2111</version>
<version>1.1.2603.2112</version>
<url>http://nexus.steamware.net/repository/SWS/GPW/stable/GPW.UI.zip</url>
<changelog>http://nexus.steamware.net/repository/SWS/GPW/stable/ChangeLog.html</changelog>
<mandatory>false</mandatory>