Fix metodi accesso dati SPEC con assistant

This commit is contained in:
Samuele Locatelli
2026-05-27 12:00:15 +02:00
parent 1eb5185240
commit e328c4e6f1
9 changed files with 351 additions and 495 deletions
+7
View File
@@ -0,0 +1,7 @@
namespace MP.Core.DTO
{
public class CountResultDto
{
public int NumCount { get; set; }
}
}
+90 -3
View File
@@ -371,9 +371,9 @@ namespace MP.Data.Controllers
/// Elenco valori ammessi x Tipo articoli
/// </summary>
/// <returns></returns>
public List<ListValuesModel> AnagTipoArtLV()
public Task<List<ListValuesModel>> AnagTipoArtLvAsync()
{
return ListValuesFilt("AnagArticoli", "Tipo");
return ListValuesFiltAsync("AnagArticoli", "Tipo");
}
/// <summary>
@@ -395,6 +395,35 @@ namespace MP.Data.Controllers
return dbResult;
}
/// <summary>
/// Conteggio num articoli Async
/// </summary>
/// <returns></returns>
public async Task<int> ArticoliCountAsync()
{
using var dbCtx = new MoonProContext(options);
var result = await dbCtx
.DbSetArticoli
.CountAsync();
return result;
}
/// <summary>
/// Elenco tabella Articoli IMPIEGATI (da stored stp_ART_getUsed) Async
/// </summary>
/// <returns></returns>
public async Task<int> ArticoliCountUsedAsync()
{
using var dbCtx = new MoonProContext(options);
var result = await dbCtx
.DbSetCounter
.FromSqlRaw("EXEC stp_ART_CountUsed")
.AsNoTracking()
.ToListAsync();
return result.FirstOrDefault()?.NumCount ?? 0;
}
/// <summary>
/// Eliminazione Record
/// </summary>
@@ -492,6 +521,24 @@ namespace MP.Data.Controllers
return dbResult;
}
/// <summary>
/// Elenco tabella Articoli NON IMPIEGATI (da stored stp_ART_getUsed) Async
/// </summary>
/// <returns></returns>
public async Task<List<AnagArticoliModel>> ArticoliGetUnusedAsync()
{
List<AnagArticoliModel> dbResult = new List<AnagArticoliModel>();
using (var dbCtx = new MoonProContext(options))
{
dbResult = await dbCtx
.DbSetArticoli
.FromSqlRaw("EXEC stp_ART_getNotUsed")
.AsNoTracking()
.ToListAsync();
}
return dbResult;
}
/// <summary>
/// Elenco tabella Articoli IMPIEGATI (da stored stp_ART_getUsed)
/// </summary>
@@ -510,6 +557,24 @@ namespace MP.Data.Controllers
return dbResult;
}
/// <summary>
/// Elenco tabella Articoli IMPIEGATI (da stored stp_ART_getUsed) Async
/// </summary>
/// <returns></returns>
public async Task<List<AnagArticoliModel>> ArticoliGetUsedAsync()
{
List<AnagArticoliModel> dbResult = new List<AnagArticoliModel>();
using (var dbCtx = new MoonProContext(options))
{
dbResult = await dbCtx
.DbSetArticoli
.FromSqlRaw("EXEC stp_ART_getUsed")
.AsNoTracking()
.ToListAsync();
}
return dbResult;
}
/// <summary>
/// Update Record
/// </summary>
@@ -569,6 +634,24 @@ namespace MP.Data.Controllers
return dbResult;
}
/// <summary>
/// Elenco da tabella Config Async
/// </summary>
/// <returns></returns>
public async Task<List<ConfigModel>> ConfigGetAllAsync()
{
List<ConfigModel> dbResult = new List<ConfigModel>();
using (var dbCtx = new MoonProContext(options))
{
dbResult = await dbCtx
.DbSetConfig
.AsNoTracking()
.OrderBy(x => x.Chiave)
.ToListAsync();
}
return dbResult;
}
/// <summary>
/// Update record config
/// </summary>
@@ -1435,6 +1518,7 @@ namespace MP.Data.Controllers
}
return dbResult;
}
/// <summary>
/// Elenco PODL per composizione KIT non avviati filtrati x articolo, KeyRich (che contiene stato)
/// </summary>
@@ -1492,6 +1576,7 @@ namespace MP.Data.Controllers
}
return dbResult;
}
/// <summary>
/// Elenco PODL non avviati filtrati x articolo, KeyRich (che contiene stato) - ASYNC
/// </summary>
@@ -1521,6 +1606,7 @@ namespace MP.Data.Controllers
return dbResult;
}
#if false
/// <summary>
/// Elenco valori ammessi x tabella/colonna
/// </summary>
@@ -1540,7 +1626,8 @@ namespace MP.Data.Controllers
.ToList();
}
return dbResult;
}
}
#endif
/// <summary>
/// Elenco valori ammessi x tabella/colonna Async
+6
View File
@@ -1,5 +1,6 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using MP.Core.DTO;
using MP.Data.DbModels;
using MP.Data.DbModels.Anag;
using NLog;
@@ -41,6 +42,7 @@ namespace MP.Data
#region Public Properties
public virtual DbSet<CountResultDto> DbSetCounter { get; set; }
public virtual DbSet<DbSizeModel> DbSetDbSize { get; set; }
public virtual DbSet<AlarmLogModel> DbSetAlarmLog { get; set; }
@@ -155,6 +157,10 @@ namespace MP.Data
{
modelBuilder.HasAnnotation("Relational:Collation", "SQL_Latin1_General_CP1_CI_AS");
modelBuilder.Entity<CountResultDto>().HasNoKey();
modelBuilder.Entity<StatsAnagArticoli>(entity =>
{
entity.HasKey(e => e.CodArticolo);
+165 -404
View File
@@ -82,20 +82,18 @@ namespace MP.SPEC.Data
#endregion Public Events
#region Public Properties
public static MpSpecController dbController { get; set; } = null!;
public static MpMongoController mongoController { get; set; } = null!;
public MessagePipe BroadastMsgPipe { get; set; } = null!;
/// <summary>
/// Dizionario dei tag configurati per IOB
/// </summary>
public Dictionary<string, List<TagData>> currTagConf { get; set; } = new Dictionary<string, List<TagData>>();
// Cache per controllo eliminazione articoli (Smart HashSet approach)
private HashSet<string> _usedArtIdsCache = new();
private HashSet<string> _unusedArtIdsCache = new();
private DateTime _artCacheExpiry = DateTime.MinValue;
#endregion Public Properties
#region Public Methods
/// <summary>
@@ -312,41 +310,9 @@ namespace MP.SPEC.Data
);
}
#if false
public async Task<List<ListValuesModel>> AnagStatiComm()
public async Task<List<ListValuesModel>> AnagTipoArtLvAsync()
{
using var activity = ActivitySource.StartActivity("AnagStatiComm");
string source = "DB";
List<ListValuesModel>? result = new List<ListValuesModel>();
// cerco in redis...
RedisValue rawData = await redisDb.StringGetAsync(Utils.redisStatoCom);
if (!string.IsNullOrEmpty($"{rawData}"))
{
result = JsonConvert.DeserializeObject<List<ListValuesModel>>($"{rawData}");
source = "REDIS";
}
else
{
result = await Task.FromResult(dbController.AnagStatiComm());
// serializzo e salvo...
rawData = JsonConvert.SerializeObject(result);
await redisDb.StringSetAsync(Utils.redisStatoCom, rawData, getRandTOut(redisLongTimeCache));
}
if (result == null)
{
result = new List<ListValuesModel>();
}
activity?.SetTag("data.source", source);
activity?.SetTag("result.count", result.Count);
activity?.Stop();
LogTrace($"AnagStatiComm Read from {source}: {activity?.Duration.TotalMilliseconds}ms");
return result;
}
#endif
public async Task<List<ListValuesModel>> AnagTipoArtLV()
{
using var activity = ActivitySource.StartActivity("AnagStatiCommAsync");
using var activity = ActivitySource.StartActivity("AnagTipoArtLvAsync");
string source = "DB";
List<ListValuesModel>? result = new List<ListValuesModel>();
// cerco in redis...
@@ -358,7 +324,7 @@ namespace MP.SPEC.Data
}
else
{
result = await Task.FromResult(dbController.AnagTipoArtLV());
result = await dbController.AnagTipoArtLvAsync();
// serializzo e salvo...
rawData = JsonConvert.SerializeObject(result);
await redisDb.StringSetAsync(Utils.redisTipoArt, rawData, getRandTOut(redisLongTimeCache));
@@ -370,7 +336,7 @@ namespace MP.SPEC.Data
activity?.SetTag("data.source", source);
activity?.SetTag("result.count", result.Count);
activity?.Stop();
LogTrace($"AnagTipoArtLV Read from {source}: {activity?.Duration.TotalMilliseconds}ms");
LogTrace($"AnagTipoArtLvAsync Read from {source}: {activity?.Duration.TotalMilliseconds}ms");
return result;
}
@@ -437,7 +403,6 @@ namespace MP.SPEC.Data
string sKey = string.IsNullOrWhiteSpace(tipo) ? "ALL" : tipo.Trim();
string redisKey = $"{Utils.redisArtList}:{azienda}:Tipo:{sKey}";
string memKey = $"MEM:{redisKey}";
return await GetOrFetchAsync(
operationName: "ArticoliGetByTipoAsync",
@@ -448,53 +413,7 @@ namespace MP.SPEC.Data
?? new List<AnagArticoliModel>()
);
#if false
return await GetOrCreateCachedAsync(
operationName: "ArticoliGetByTipoAsync",
memKey: memKey,
redisKey: redisKey,
// ✅ TTL lungo (dato abbastanza statico)
memoryTtl: TimeSpan.FromMinutes(5),
dbFactory: async () =>
await dbController.ArticoliGetByTipoAsync(tipo, azienda)
?? new List<AnagArticoliModel>()
);
#endif
}
#if false
public async Task<List<AnagArticoliModel>> ArticoliGetByTipoAsync(string tipo, string azienda = "*")
{
using var activity = ActivitySource.StartActivity("ArticoliGetByTipoAsync");
List<AnagArticoliModel>? result = new List<AnagArticoliModel>();
string source = "DB";
string sKey = string.IsNullOrEmpty(tipo) ? "ALL" : tipo;
string currKey = $"{Utils.redisArtList}:{azienda}:Tipo:{sKey}";
// cerco in redis dato valore sel idxMaccSel...
RedisValue rawData = await redisDb.StringGetAsync(currKey);
if (rawData.HasValue)
{
result = JsonConvert.DeserializeObject<List<AnagArticoliModel>>($"{rawData}");
source = "REDIS";
}
else
{
result = await dbController.ArticoliGetByTipoAsync(tipo, azienda);
// serializzo e salvo...
rawData = JsonConvert.SerializeObject(result);
await redisDb.StringSetAsync(currKey, rawData, getRandTOut(redisLongTimeCache));
}
if (result == null)
{
result = new List<AnagArticoliModel>();
}
activity?.SetTag("data.source", source);
activity?.SetTag("result.count", result.Count);
activity?.Stop();
LogTrace($"ArticoliGetByTipoAsync | Read from {source}: {activity?.Duration.TotalMilliseconds}ms");
return result;
}
#endif
/// <summary>
/// Restitusice elenco articoli cercati
@@ -517,18 +436,6 @@ namespace MP.SPEC.Data
await dbController.ArticoliGetSearchAsync(numRecord, azienda, searchVal)
?? new List<AnagArticoliModel>()
);
#if false
return await GetOrCreateCachedAsync(
operationName: "ArticoliGetSearchAsync",
memKey: memKey,
redisKey: redisKey,
memoryTtl: TimeSpan.FromMinutes(2),
dbFactory: async () =>
await dbController.ArticoliGetSearchAsync(numRecord, azienda, searchVal)
?? new List<AnagArticoliModel>()
);
#endif
}
/// <summary>
@@ -550,6 +457,7 @@ namespace MP.SPEC.Data
var result = tryGet.Value!;
activity?.SetTag("data.source", source);
activity?.Stop();
LogTrace($"{operationName} | {source} | {activity?.Duration.TotalMilliseconds:F4} ms");
return result;
}
@@ -573,57 +481,11 @@ namespace MP.SPEC.Data
source = fromDb ? "DB" : "REDIS";
activity?.SetTag("data.source", source);
activity?.Stop();
LogTrace($"{operationName} | {source} | {activity?.Duration.TotalMilliseconds:F4} ms");
return final!;
}
#if false
private async Task<T> GetOrFetchAsync<T>(string operationName, string cacheKey, Func<Task<T>> fetchFunc, TimeSpan expiration)
{
using var activity = ActivitySource.StartActivity(operationName);
string source;
T result;
// ✅ 1. Tenta MEMORY / DISTRIBUTED (senza factory)
var memTry = await _cache.TryGetAsync<T>(cacheKey);
if (memTry.HasValue)
{
result = memTry.Value!;
source = "MEMORY";
}
else
{
bool fromDb = false;
// ✅ 2. fallback con factory
result = await _cache.GetOrSetAsync<T>(
cacheKey,
async _ =>
{
fromDb = true;
return await fetchFunc();
},
options => options.SetDuration(expiration)
)!;
source = fromDb ? "DB" : "REDIS";
}
// ✅ tracing e log
activity?.SetTag("data.source", source);
if (result is System.Collections.ICollection coll)
activity?.SetTag("result.count", coll.Count);
else
activity?.SetTag("result.count", result != null ? 1 : 0);
LogTrace($"{operationName} | {source} | {activity?.Duration.TotalMilliseconds:F4} ms");
return result;
}
#endif
/// <summary>
/// Cancellazione FusionCache (totale)
@@ -666,146 +528,6 @@ namespace MP.SPEC.Data
return fatto;
}
#if false
/// <summary>
/// Helper per gestione cache a 3 livelli: MEMORY, REDIS e DB con opzioni
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="operationName"></param>
/// <param name="memKey"></param>
/// <param name="redisKey"></param>
/// <param name="memoryTtl"></param>
/// <param name="dbFactory"></param>
/// <param name="serialize"></param>
/// <param name="deserialize"></param>
/// <returns></returns>
private async Task<T> GetOrCreateCachedAsync<T>(string operationName, string memKey, string redisKey, TimeSpan memoryTtl, Func<Task<T>> dbFactory, Func<T, string>? serialize = null, Func<string, T>? deserialize = null)
{
using var activity = ActivitySource.StartActivity(operationName);
string source = "NA";
T result;
string groupKey = memKey.Split(':')[0]; // es: "MACCHINE_MEM"
_memoryKeys.AddOrUpdate(
groupKey,
_ => new HashSet<string> { memKey },
(_, set) => { set.Add(memKey); return set; }
);
// ✅ default serializer (fallback)
serialize ??= (obj) => JsonConvert.SerializeObject(obj);
deserialize ??= (str) => JsonConvert.DeserializeObject<T>(str)!;
// ✅ 1. MEMORY
if (_cache.TryGetValue(memKey, out T cached))
{
result = cached;
source = "MEMORY";
}
else
{
// ✅ 2. MISS → factory
result = await _cache.GetOrCreateAsync(memKey, async entry =>
{
entry.AbsoluteExpirationRelativeToNow = memoryTtl;
// 👉 REDIS
var rawData = await redisDb.StringGetAsync(redisKey);
if (rawData.HasValue)
{
source = "REDIS";
return deserialize(rawData!);
}
// 👉 DB
source = "DB";
var dbResult = await dbFactory();
var safeResult = dbResult == null ? default! : dbResult;
await redisDb.StringSetAsync(
redisKey,
serialize(safeResult),
getRandTOut(redisLongTimeCache)
);
return safeResult;
})!;
}
// ✅ logging e tracing centralizzati
activity?.SetTag("data.source", source);
if (result is System.Collections.ICollection coll)
activity?.SetTag("result.count", coll.Count);
else
activity?.SetTag("result.count", result != null ? 1 : 0);
LogTrace($"{operationName} | {source} | {activity?.Duration.TotalMilliseconds}ms");
return result;
}
/// <summary>
/// Invalidazione di una chiave in memoria e Redis
/// </summary>
/// <param name="memKey"></param>
/// <param name="redisKey"></param>
/// <returns></returns>
public async Task InvalidateCacheAsync(string memKey, string redisKey)
{
// ✅ memoria
_cache.Remove(memKey);
// ✅ redis
await redisDb.KeyDeleteAsync(redisKey);
LogTrace($"Cache invalidated | {memKey}");
}
private readonly ConcurrentDictionary<string, HashSet<string>> _memoryKeys = new();
/// <summary>
/// Invalidazione cache da pattern
/// </summary>
/// <param name="pattern"></param>
/// <returns></returns>
public async Task InvalidateCacheByPatternAsync(string pattern)
{
// ✅ MEMORY (pattern match semplice)
var keysToRemove = _memoryKeys.Keys
.Where(k => k.Contains(pattern.Replace("*", "")))
.ToList();
foreach (var key in keysToRemove)
{
if (_memoryKeys.TryRemove(key, out var subKeys))
{
foreach (var k in subKeys)
{
_cache.Remove(k);
}
}
}
// ✅ REDIS
var masterEndpoint = redisConn.GetEndPoints()
.Where(ep => redisConn.GetServer(ep).IsConnected && !redisConn.GetServer(ep).IsReplica)
.FirstOrDefault();
var server = redisConn.GetServer(masterEndpoint);
var redisKeys = server.Keys(pattern: pattern);
foreach (var rk in redisKeys)
{
await redisDb.KeyDeleteAsync(rk);
}
LogTrace($"Cache invalidated by pattern | {pattern}");
}
#endif
/// <summary>
/// Aggiornamento record selezionato
@@ -824,6 +546,7 @@ namespace MP.SPEC.Data
return fatto;
}
/// <summary>
/// Verifica se sia possiubile cancellare articolo dato suo CodArt cercando su redis o su
/// tab veto da DB
@@ -833,69 +556,160 @@ namespace MP.SPEC.Data
public bool ArticoloDelEnabled(object CodArt)
{
using var activity = ActivitySource.StartActivity("ArticoloDelEnabled");
string source = "DB";
bool answ = false;
string codArticolo = $"{CodArt}";
int cacheCheckArtUsato = 1;
int.TryParse(_configuration.GetValue<string>("ServerConf:cacheCheckArtUsato"), out cacheCheckArtUsato);
TimeSpan TTLCache = getRandTOut(cacheCheckArtUsato);
// cerco in cache redis...
string redKeyArtUsed = $"{Utils.redKeyArtUsed}:{codArticolo}";
string redKeyTabCheckArt = Utils.redKeyTabCheckArt;
var rawData = redisDb.StringGet(redKeyArtUsed);
if (!string.IsNullOrEmpty(rawData))
int numUsed = _usedArtIdsCache.Count;
int numUnused = _unusedArtIdsCache.Count;
bool usato = true;
string source = "MEMORY";
// 1. Controllo immediato sulla cache locale (HashSet) x eventuale refresh
if (DateTime.Now >= _artCacheExpiry || (numUsed + numUnused) <= 0)
{
bool.TryParse(rawData, out answ);
source = "DB/REDIS";
// Fallback sincrono minimo per non rompere il componente Blazor
// Nota: Questo è un workaround per la firma sincrona.
var task = EnsureArtCacheLoadedAsync(false);
task.Wait();
// rileggo
numUsed = _usedArtIdsCache.Count;
numUnused = _unusedArtIdsCache.Count;
}
// verifico quale sia l'elenco
if (numUsed > 0)
{
usato = _usedArtIdsCache.Contains(codArticolo);
}
else
{
// controllo non sia stato mai prodotto sennò non posso cancellare...
try
{
// cerco in cache se ci sia la tabella con gli articoli impiegati...
var rawTable = redisDb.StringGet(redKeyTabCheckArt);
List<string>? artList = new List<string>();
if (!string.IsNullOrEmpty(rawTable))
{
artList = JsonConvert.DeserializeObject<List<string>>($"{rawTable}");
}
// rileggo...
if (artList == null || artList.Count == 0)
{
artList = new List<string>();
var tabArticoli = dbController.ArticoliGetUsed();
var codList = tabArticoli.Select(x => x.CodArticolo);
foreach (string cod in codList)
{
artList.Add(cod);
}
// SE fosse vuoto aggiungo comunque il cado "ND"...
if (artList.Count == 0)
{
artList.Add("ND");
}
// salvo
rawTable = JsonConvert.SerializeObject(artList);
redisDb.StringSet(redKeyTabCheckArt, rawTable, TTLCache);
}
// cerco nella tabella: se ci fosse --> disabilitato delete
bool usato = false;
if (artList != null && artList.Count > 0)
{
usato = artList.Contains(codArticolo);
}
answ = !usato;
redisDb.StringSet(redKeyArtUsed, $"{answ}", TTLCache);
}
catch
{ }
usato = !_unusedArtIdsCache.Contains(codArticolo);
}
activity?.SetTag("data.source", source);
activity?.Stop();
LogTrace($"ArticoloDelEnabled | Read from {source}: {activity?.Duration.TotalMilliseconds}ms");
return answ;
LogTrace($"ArticoloDelEnabled | Cod: {codArticolo} | Source: {source}");
return !usato;
}
/// <summary>
/// Caricamento asincrono della cache degli articoli (Used/Unused)
/// </summary>
public async Task EnsureArtCacheLoadedAsync(bool forceReload)
{
if (!forceReload && (DateTime.Now < _artCacheExpiry && (_usedArtIdsCache.Count > 0 || _unusedArtIdsCache.Count > 0)))
return;
try
{
// verifico quale sia il set + piccolo
int totalCount = await dbController.ArticoliCountAsync();
int usedCount = await dbController.ArticoliCountUsedAsync();
if (usedCount <= (totalCount - usedCount))
{
var usedList = await dbController.ArticoliGetUsedAsync();
_usedArtIdsCache = new HashSet<string>(usedList.Select(x => x.CodArticolo));
_unusedArtIdsCache.Clear();
}
else
{
var unusedList = await dbController.ArticoliGetUnusedAsync();
_unusedArtIdsCache = new HashSet<string>(unusedList.Select(x => x.CodArticolo));
_usedArtIdsCache.Clear();
}
_artCacheExpiry = DateTime.Now.AddMinutes(15); // TTL ragionevole per la cache locale
}
catch (Exception ex)
{
Log.Error($"Errore nel caricamento cache articoli: {ex.Message}");
_artCacheExpiry = DateTime.Now.AddSeconds(1); // Retry breve in caso di errore
}
}
#if false
/// <summary>
/// Verifica se sia possiubile cancellare articolo dato suo CodArt cercando su redis o su
/// tab veto da DB
/// </summary>
/// <param name="CodArt"></param>
/// <returns></returns>
public async Task<bool> ArticoloDelEnabledAsync(object CodArt)
{
using var activity = ActivitySource.StartActivity("ArticoloDelEnabledAsync");
string codArticolo = $"{CodArt}";
int cacheCheckArtUsato = 1;
int.TryParse(_configuration.GetValue<string>("ServerConf:cacheCheckArtUsato"), out cacheCheckArtUsato);
TimeSpan ttl = getRandTOut(cacheCheckArtUsato);
// 1. Controllo cache locale (Smart HashSet)
// Se siamo nel periodo di validità della cache locale, facciamo il controllo istantaneo
if (DateTime.Now < _artCacheExpiry)
{
// Se la nostra cache corrente contiene l'ID (o se stiamo usando la lista degli unused)
// Nota: la logica dipende da quale lista è stata caricata.
// Per semplicità, se abbiamo caricato gli "usati", cerchiamo tra quelli.
// Se abbiamo caricato gli "unused", l'articolo è eliminabile se è nel set.
// Ma per evitare confusione, gestiamo il refresh globale.
}
// 2. Fallback su GetOrFetchAsync per garantire la sincronizzazione e l'uso di FusionCache (L1/L2)
// Usiamo un approccio che carichi la lista più piccola in memoria.
// Per evitare complessità di switching lato client, usiamo la lista degli "usati" come riferimento
// principale nella cache distribuita, ma ottimizziamo il caricamento.
bool usato = false;
string source = "DB";
// Controllo se l'ID è già presente nella nostra cache locale degli "usati"
if (DateTime.Now < _artCacheExpiry && _usedArtIdsCache.Contains(codArticolo))
{
usato = true;
source = "MEMORY(USED)";
}
else if (DateTime.Now < _artCacheExpiry && _unusedArtIdsCache.Contains(codArticolo))
{
usato = false;
source = "MEMORY(UNUSED)";
}
else
{
// Cache scaduta o non presente: ricalcoliamo la strategia
int totalCount = await dbController.ArticoliCountAsync();
int usedCount = await dbController.ArticoliCountUsedAsync();
if (usedCount <= (totalCount - usedCount))
{
// Gli usati sono meno o uguali agli unused: carichiamo gli usati
var usedList = await dbController.ArticoliGetUsedAsync();
_usedArtIdsCache = new HashSet<string>(usedList.Select(x => x.CodArticolo));
_unusedArtIdsCache.Clear();
usato = _usedArtIdsCache.Contains(codArticolo);
source = "DB+MEMORY(USED)";
}
else
{
// Gli unused sono meno: carichiamo gli unused
var unusedList = await dbController.ArticoliGetUnusedAsync();
_unusedArtIdsCache = new HashSet<string>(unusedList.Select(x => x.CodArticolo));
_usedArtIdsCache.Clear();
usato = !_unusedArtIdsCache.Contains(codArticolo);
source = "DB+MEMORY(UNUSED)";
}
_artCacheExpiry = DateTime.Now.AddMinutes(cacheCheckArtUsato);
}
bool answ = !usato;
activity?.SetTag("data.source", source);
activity?.Stop();
LogTrace($"ArticoloDelEnabledAsync | Cod: {codArticolo} | Usato: {usato} | {activity?.Duration.TotalMilliseconds}ms");
return answ;
}
#endif
public string CalcRecipe(RecipeModel currRecipe)
{
using var activity = ActivitySource.StartActivity("CalcRecipe");
@@ -956,7 +770,7 @@ namespace MP.SPEC.Data
}
else
{
result = await Task.FromResult(dbController.ConfigGetAll());
result = await dbController.ConfigGetAllAsync();
// serializzo e salvo...
rawData = JsonConvert.SerializeObject(result);
await redisDb.StringSetAsync(Utils.redisConfKey, rawData, getRandTOut(redisLongTimeCache));
@@ -1198,18 +1012,18 @@ namespace MP.SPEC.Data
}
/// <summary>
/// Restitusice elenco aziende
/// Restituisce elenco aziende
/// </summary>
/// <returns></returns>
public async Task<List<AnagGruppiModel>> ElencoAziendeAsync()
{
using var activity = ActivitySource.StartActivity("ElencoAziendeAsync");
string source = "DB";
var listAz = await dbController.AnagGruppiAziendeAsync();
activity?.SetTag("data.source", source);
activity?.Stop();
LogTrace($"ElencoAziendeAsync | Read from {source}: {activity?.Duration.TotalMilliseconds}ms");
return listAz;
return await GetOrFetchAsync(
operationName: "ElencoAziendeAsync",
cacheKey: $"{Utils.redisAnagGruppi}:Aziende",
expiration: TimeSpan.FromMinutes(redisLongTimeCache * 2),
fetchFunc: async () =>
await dbController.AnagGruppiAziendeAsync() ?? new List<AnagGruppiModel>()
);
}
/// <summary>
@@ -2898,9 +2712,6 @@ namespace MP.SPEC.Data
{
string redisKey = $"{Utils.redisPOdlList}_kit:{codGruppo}:{idxMacchina}:{keyRichPart}:{lanciato}:{startDate:yyyyMMdd_HHmmss}:{endDate:yyyyMMdd_HHmmss}";
// ✅ stessa chiave per memoria (puoi anche prefissare)
string memKey = $"MEM:{redisKey}";
return await GetOrFetchAsync(
operationName: "POdlToKitListGetFiltAsync",
cacheKey: redisKey,
@@ -2916,57 +2727,7 @@ namespace MP.SPEC.Data
) ?? new List<PODLExpModel>()
);
#if false
return await GetOrCreateCachedAsync(
operationName: "POdlToKitListGetFiltAsync",
memKey: memKey,
redisKey: redisKey,
// ✅ TTL RAM breve (coerente con redisShortTimeCache)
memoryTtl: TimeSpan.FromSeconds(redisShortTimeCache),
dbFactory: async () =>
await dbController.ListPODL_KitFiltAsync(
lanciato,
keyRichPart,
idxMacchina,
codGruppo,
startDate,
endDate
) ?? new List<PODLExpModel>()
);
#endif
}
#if false
public async Task<List<PODLExpModel>> POdlToKitListGetFiltAsync(bool lanciato, string keyRichPart, string idxMacchina, string codGruppo, DateTime startDate, DateTime endDate)
{
using var activity = ActivitySource.StartActivity("POdlToKitListGetFiltAsync");
List<PODLExpModel>? result = new List<PODLExpModel>();
string source = "DB";
string currKey = $"{Utils.redisPOdlList}_kit:{codGruppo}:{idxMacchina}:{keyRichPart}:{lanciato}:{startDate:yyyyMMdd_HHmmss}:{endDate:yyyyMMdd_HHmmss}";
// cerco in redis dato valore sel idxMaccSel...
RedisValue rawData = redisDb.StringGet(currKey);
if (rawData.HasValue)
{
result = JsonConvert.DeserializeObject<List<PODLExpModel>>($"{rawData}");
source = "REDIS";
}
else
{
result = await dbController.ListPODL_KitFiltAsync(lanciato, keyRichPart, idxMacchina, codGruppo, startDate, endDate);
// serializzo e salvo...
rawData = JsonConvert.SerializeObject(result);
redisDb.StringSet(currKey, rawData, TimeSpan.FromSeconds(redisShortTimeCache));
}
if (result == null)
{
result = new List<PODLExpModel>();
}
activity?.SetTag("data.source", source);
activity?.SetTag("result.count", result.Count);
activity?.Stop();
LogTrace($"POdlToKitListGetFiltAsync | Read from {source}: {activity?.Duration.TotalMilliseconds}ms");
return result;
}
#endif
/// <summary>
/// Chiamata salvataggio ricetta + refresh REDIS
+1 -1
View File
@@ -5,7 +5,7 @@
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<RootNamespace>MP.SPEC</RootNamespace>
<Version>8.16.2605.2709</Version>
<Version>8.16.2605.2711</Version>
<UserSecretsId>1800a78a-6ff1-40f9-b490-87fb8bfc1394</UserSecretsId>
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
</PropertyGroup>
+79 -84
View File
@@ -16,8 +16,6 @@ namespace MP.SPEC.Pages
: "";
}
private SelectArticoliParams currFilter = new SelectArticoliParams();
public void Dispose()
{
currRecord = null;
@@ -51,51 +49,6 @@ namespace MP.SPEC.Pages
[Inject]
protected NavigationManager NavManager { get; set; } = null!;
protected int totalCount
{
get
{
int answ = 0;
if (SearchRecords != null)
{
answ = SearchRecords.Count;
}
return answ;
}
}
#endregion Protected Properties
#region Protected Methods
private bool isNewArt = false;
/// <summary>
/// Crea nuovo record e va in editing...
/// </summary>
/// <returns></returns>
protected async Task addNew()
{
isNewArt = true;
currRecord = new AnagArticoliModel()
{
CodArticolo = $"_NEW_{DateTime.Now:yyyyMMdd.HHmmss}",
DescArticolo = "Nuovo articolo",
Azienda = selAzienda != "*" ? selAzienda : "MAPO",
Disegno = "",
Tipo = "ART",
CurrRev = "",
ProdRev = ""
};
await Task.Delay(1);
}
protected async Task resetSearch()
{
SearchVal = "";
await ReloadData();
}
protected string searchCss
{
get => string.IsNullOrEmpty(searchVal) ? "btn-secondary" : "btn-primary";
@@ -123,9 +76,43 @@ namespace MP.SPEC.Pages
}
}
}
private string searchVal { get; set; } = "";
private string chkDisabled => isNewArt ? "" : "disabled";
protected int totalCount
{
get
{
int answ = 0;
if (SearchRecords != null)
{
answ = SearchRecords.Count;
}
return answ;
}
}
#endregion Protected Properties
#region Protected Methods
/// <summary>
/// Crea nuovo record e va in editing...
/// </summary>
/// <returns></returns>
protected async Task addNew()
{
isNewArt = true;
currRecord = new AnagArticoliModel()
{
CodArticolo = $"_NEW_{DateTime.Now:yyyyMMdd.HHmmss}",
DescArticolo = "Nuovo articolo",
Azienda = selAzienda != "*" ? selAzienda : "MAPO",
Disegno = "",
Tipo = "ART",
CurrRev = "",
ProdRev = ""
};
await Task.Delay(1);
}
protected async Task cancel()
{
@@ -134,6 +121,24 @@ namespace MP.SPEC.Pages
await Task.Delay(1);
}
protected async Task cloneRecord(AnagArticoliModel selRec)
{
isNewArt = true;
// creo record duplicato...
AnagArticoliModel newRec = new AnagArticoliModel()
{
Azienda = selRec.Azienda,
CodArticolo = $"clone-{selRec.CodArticolo}",
DescArticolo = $"CLONE - {selRec.DescArticolo}",
Disegno = selRec.Disegno,
Tipo = selRec.Tipo,
CurrRev = "",
ProdRev = ""
};
currRecord = newRec;
await Task.Delay(1);
}
/// <summary>
/// Eliminazione record selezionato (previa conferma)
/// </summary>
@@ -163,21 +168,7 @@ namespace MP.SPEC.Pages
protected override async Task OnInitializedAsync()
{
numRecord = 10;
#if false
configData = await MDService.ConfigGetAll();
var currRec = configData.FirstOrDefault(x => x.Chiave == "AZIENDA");
if (currRec != null)
{
selAzienda = currRec.Valore;
}
#endif
selAzienda = await MDService.ConfigTryGetAsync("AZIENDA");
if (string.IsNullOrEmpty(selAzienda))
{
selAzienda = "*";
}
ListAziende = await MDService.ElencoAziendeAsync();
ListTipoArt = await MDService.AnagTipoArtLV();
await ReloadBaseData();
}
protected override async Task OnParametersSetAsync()
@@ -191,6 +182,12 @@ namespace MP.SPEC.Pages
currRecord = null;
}
protected async Task resetSearch()
{
SearchVal = "";
await ReloadData();
}
protected async Task resetSel()
{
isNewArt = false;
@@ -204,24 +201,6 @@ namespace MP.SPEC.Pages
currRecord = selRec;
await Task.Delay(1);
}
protected async Task cloneRecord(AnagArticoliModel selRec)
{
isNewArt = true;
// creo record duplicato...
AnagArticoliModel newRec = new AnagArticoliModel()
{
Azienda = selRec.Azienda,
CodArticolo = $"clone-{selRec.CodArticolo}",
DescArticolo = $"CLONE - {selRec.DescArticolo}",
Disegno = selRec.Disegno,
Tipo = selRec.Tipo,
CurrRev = "",
ProdRev = ""
};
currRecord = newRec;
await Task.Delay(1);
}
protected async Task update(AnagArticoliModel selRec)
{
@@ -245,7 +224,9 @@ namespace MP.SPEC.Pages
#region Private Fields
private string _selAzienda = "*";
private SelectArticoliParams currFilter = new SelectArticoliParams();
private AnagArticoliModel? currRecord = null;
private bool isNewArt = false;
private List<AnagGruppiModel>? ListAziende;
private List<AnagArticoliModel>? ListRecords;
private List<ListValuesModel>? ListTipoArt;
@@ -257,6 +238,7 @@ namespace MP.SPEC.Pages
private int _currPage { get; set; } = 1;
private int _numRecord { get; set; } = 10;
private string chkDisabled => isNewArt ? "" : "disabled";
private int currPage
{
@@ -288,6 +270,8 @@ namespace MP.SPEC.Pages
}
}
private string searchVal { get; set; } = "";
private string selAzienda
{
get => _selAzienda;
@@ -328,8 +312,19 @@ namespace MP.SPEC.Pages
/// <returns></returns>
private bool ArticoloDelEnabled(string codArt)
{
bool answ = MDService.ArticoloDelEnabled(codArt);
return answ;
return MDService.ArticoloDelEnabled(codArt);
}
private async Task ReloadBaseData()
{
await MDService.EnsureArtCacheLoadedAsync(true);
selAzienda = await MDService.ConfigTryGetAsync("AZIENDA");
if (string.IsNullOrEmpty(selAzienda))
{
selAzienda = "*";
}
ListAziende = await MDService.ElencoAziendeAsync();
ListTipoArt = await MDService.AnagTipoArtLvAsync();
}
private async Task ReloadData()
+1 -1
View File
@@ -1,6 +1,6 @@
<body>
<i>Modulo MAPOSPEC </i>
<h4>Versione: 8.16.2605.2709</h4>
<h4>Versione: 8.16.2605.2711</h4>
<br /> Note di rilascio:
<ul>
<li>
+1 -1
View File
@@ -1 +1 @@
8.16.2605.2709
8.16.2605.2711
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<item>
<version>8.16.2605.2709</version>
<version>8.16.2605.2711</version>
<url>https://nexus.steamware.net/repository/SWS/MP-SPEC/stable/LAST/MP.SPEC.zip</url>
<changelog>https://nexus.steamware.net/repository/SWS/MP-SPEC/stable/LAST/ChangeLog.html</changelog>
<mandatory>false</mandatory>