feat: Implementato sistema di associazioni chiave per prevenire duplicati nel data coupling
BREAKING CHANGE: Rimosso completamente il vecchio sistema RecordAssociation Modifiche principali: - Sostituito RecordAssociation con KeyAssociation basato sui valori delle chiavi - Implementata logica robusta di UPDATE vs INSERT basata su associazioni esistenti - Aggiunta normalizzazione delle chiavi (.Trim()) per consistenza - Implementato fallback nella ricerca associazioni per maggiore affidabilità - Sostituita verifica pre-UPDATE con tentativo diretto più efficiente Componenti modificati: - Nuovo modello: KeyAssociation.cs con campi ottimizzati - Nuovo servizio: KeyAssociationService.cs con metodi completi - Aggiornato: DataCoupler.razor con logica migliorata di gestione associazioni - Aggiornato: CredentialDbContext per gestire solo KeyAssociations - Aggiornati: tutti i servizi di interfaccia per supportare il nuovo sistema - Creata: pagina KeyAssociations.razor per gestione associazioni - Aggiornato: NavMenu.razor con link alla gestione associazioni Miglioramenti tecnici: - Logica di UPDATE più robusta: tenta direttamente l'aggiornamento invece di verificare prima l'esistenza - Gestione errori migliorata con cleanup automatico delle associazioni non valide - Debug logging estensivo per troubleshooting - Fallback nella ricerca associazioni se parametri specifici falliscono - Normalizzazione valori chiave per prevenire problemi di whitespace Risultato: Il sistema ora previene correttamente i duplicati utilizzando le associazioni per decidere se fare INSERT (nuovo record) o UPDATE (record esistente) basandosi sui valori delle chiavi. Database: - Creata migrazione EF per rimuovere RecordAssociations e aggiungere KeyAssociations - Eliminati file e codice legacy non più necessari
This commit is contained in:
@@ -68,16 +68,16 @@ public class DatabaseInitializer : IDatabaseInitializer
|
||||
await _context.Credentials.CountAsync();
|
||||
_logger.LogInformation("Tabella Credentials verificata con successo");
|
||||
|
||||
// Verifica se la tabella RecordAssociations esiste, se non esiste la crea senza ricreare tutto il database
|
||||
// Verifica se la tabella KeyAssociations esiste, se non esiste la crea senza ricreare tutto il database
|
||||
try
|
||||
{
|
||||
await _context.RecordAssociations.CountAsync();
|
||||
_logger.LogInformation("Tabella RecordAssociations verificata con successo");
|
||||
await _context.KeyAssociations.CountAsync();
|
||||
_logger.LogInformation("Tabella KeyAssociations verificata con successo");
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
_logger.LogInformation("Tabella RecordAssociations non trovata, creazione tramite migrazione...");
|
||||
await CreateRecordAssociationsTableAsync();
|
||||
_logger.LogInformation("Tabella KeyAssociations non trovata, creazione tramite migrazione...");
|
||||
await CreateKeyAssociationsTableAsync();
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
@@ -170,60 +170,78 @@ public class DatabaseInitializer : IDatabaseInitializer
|
||||
_logger.LogInformation("Colonna RestServiceType aggiunta con successo");
|
||||
}
|
||||
|
||||
// Migrazione 2: Verifica se la tabella RecordAssociations esiste
|
||||
// Migrazione 2: Elimina vecchia tabella RecordAssociations se esiste e crea KeyAssociations
|
||||
try
|
||||
{
|
||||
// Prova a eliminare la vecchia tabella se esiste
|
||||
await _context.Database.ExecuteSqlRawAsync("DROP TABLE IF EXISTS RecordAssociations");
|
||||
_logger.LogInformation("Vecchia tabella RecordAssociations eliminata");
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Ignora errori se la tabella non esiste
|
||||
}
|
||||
|
||||
// Verifica se la tabella KeyAssociations esiste
|
||||
try
|
||||
{
|
||||
await _context.Database.ExecuteSqlRawAsync(
|
||||
"SELECT COUNT(*) FROM RecordAssociations LIMIT 1");
|
||||
_logger.LogInformation("Tabella RecordAssociations già presente");
|
||||
"SELECT COUNT(*) FROM KeyAssociations LIMIT 1");
|
||||
_logger.LogInformation("Tabella KeyAssociations già presente");
|
||||
}
|
||||
catch (Microsoft.Data.Sqlite.SqliteException)
|
||||
{
|
||||
// La tabella non esiste, la creiamo
|
||||
_logger.LogInformation("Creazione tabella RecordAssociations...");
|
||||
_logger.LogInformation("Creazione tabella KeyAssociations...");
|
||||
|
||||
// Crea la tabella
|
||||
await _context.Database.ExecuteSqlRawAsync(@"
|
||||
CREATE TABLE RecordAssociations (
|
||||
CREATE TABLE KeyAssociations (
|
||||
Id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
SourceName TEXT NOT NULL,
|
||||
SourceType TEXT NOT NULL,
|
||||
SourceKey TEXT NOT NULL,
|
||||
KeyValue TEXT NOT NULL,
|
||||
SourceKeyField TEXT NOT NULL,
|
||||
DestinationKeyField TEXT NOT NULL,
|
||||
DestinationEntity TEXT NOT NULL,
|
||||
DestinationId TEXT NOT NULL,
|
||||
RestCredentialName TEXT NOT NULL,
|
||||
CreatedAt TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
UpdatedAt TEXT,
|
||||
LastVerifiedAt TEXT,
|
||||
IsActive INTEGER NOT NULL DEFAULT 1,
|
||||
SourcesInfo TEXT,
|
||||
AdditionalInfo TEXT
|
||||
)");
|
||||
|
||||
// Crea gli indici
|
||||
await _context.Database.ExecuteSqlRawAsync(@"
|
||||
CREATE UNIQUE INDEX IX_RecordAssociations_Unique
|
||||
ON RecordAssociations (SourceName, SourceKey, DestinationEntity)");
|
||||
CREATE INDEX IX_KeyAssociations_KeyValue
|
||||
ON KeyAssociations (KeyValue)");
|
||||
|
||||
await _context.Database.ExecuteSqlRawAsync(@"
|
||||
CREATE INDEX IX_RecordAssociations_SourceType
|
||||
ON RecordAssociations (SourceType)");
|
||||
CREATE UNIQUE INDEX IX_KeyAssociations_Unique
|
||||
ON KeyAssociations (KeyValue, DestinationEntity, RestCredentialName)");
|
||||
|
||||
await _context.Database.ExecuteSqlRawAsync(@"
|
||||
CREATE INDEX IX_RecordAssociations_DestinationEntity
|
||||
ON RecordAssociations (DestinationEntity)");
|
||||
CREATE INDEX IX_KeyAssociations_DestinationEntity
|
||||
ON KeyAssociations (DestinationEntity)");
|
||||
|
||||
await _context.Database.ExecuteSqlRawAsync(@"
|
||||
CREATE INDEX IX_RecordAssociations_RestCredentialName
|
||||
ON RecordAssociations (RestCredentialName)");
|
||||
CREATE INDEX IX_KeyAssociations_RestCredentialName
|
||||
ON KeyAssociations (RestCredentialName)");
|
||||
|
||||
await _context.Database.ExecuteSqlRawAsync(@"
|
||||
CREATE INDEX IX_RecordAssociations_IsActive
|
||||
ON RecordAssociations (IsActive)");
|
||||
CREATE INDEX IX_KeyAssociations_IsActive
|
||||
ON KeyAssociations (IsActive)");
|
||||
|
||||
await _context.Database.ExecuteSqlRawAsync(@"
|
||||
CREATE INDEX IX_RecordAssociations_CreatedAt
|
||||
ON RecordAssociations (CreatedAt)");
|
||||
CREATE INDEX IX_KeyAssociations_CreatedAt
|
||||
ON KeyAssociations (CreatedAt)");
|
||||
|
||||
_logger.LogInformation("Tabella RecordAssociations creata con successo");
|
||||
await _context.Database.ExecuteSqlRawAsync(@"
|
||||
CREATE INDEX IX_KeyAssociations_LastVerifiedAt
|
||||
ON KeyAssociations (LastVerifiedAt)");
|
||||
|
||||
_logger.LogInformation("Tabella KeyAssociations creata con successo");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -233,58 +251,67 @@ public class DatabaseInitializer : IDatabaseInitializer
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CreateRecordAssociationsTableAsync()
|
||||
private async Task CreateKeyAssociationsTableAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("Creazione tabella RecordAssociations...");
|
||||
_logger.LogInformation("Creazione tabella KeyAssociations...");
|
||||
|
||||
// Crea la tabella
|
||||
// Elimina la vecchia tabella se esiste
|
||||
await _context.Database.ExecuteSqlRawAsync("DROP TABLE IF EXISTS RecordAssociations");
|
||||
|
||||
// Crea la nuova tabella
|
||||
await _context.Database.ExecuteSqlRawAsync(@"
|
||||
CREATE TABLE RecordAssociations (
|
||||
CREATE TABLE KeyAssociations (
|
||||
Id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
SourceName TEXT NOT NULL,
|
||||
SourceType TEXT NOT NULL,
|
||||
SourceKey TEXT NOT NULL,
|
||||
KeyValue TEXT NOT NULL,
|
||||
SourceKeyField TEXT NOT NULL,
|
||||
DestinationKeyField TEXT NOT NULL,
|
||||
DestinationEntity TEXT NOT NULL,
|
||||
DestinationId TEXT NOT NULL,
|
||||
RestCredentialName TEXT NOT NULL,
|
||||
CreatedAt TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
UpdatedAt TEXT,
|
||||
LastVerifiedAt TEXT,
|
||||
IsActive INTEGER NOT NULL DEFAULT 1,
|
||||
SourcesInfo TEXT,
|
||||
AdditionalInfo TEXT
|
||||
)");
|
||||
|
||||
// Crea gli indici
|
||||
await _context.Database.ExecuteSqlRawAsync(@"
|
||||
CREATE UNIQUE INDEX IX_RecordAssociations_Unique
|
||||
ON RecordAssociations (SourceName, SourceKey, DestinationEntity)");
|
||||
CREATE INDEX IX_KeyAssociations_KeyValue
|
||||
ON KeyAssociations (KeyValue)");
|
||||
|
||||
await _context.Database.ExecuteSqlRawAsync(@"
|
||||
CREATE INDEX IX_RecordAssociations_SourceType
|
||||
ON RecordAssociations (SourceType)");
|
||||
CREATE UNIQUE INDEX IX_KeyAssociations_Unique
|
||||
ON KeyAssociations (KeyValue, DestinationEntity, RestCredentialName)");
|
||||
|
||||
await _context.Database.ExecuteSqlRawAsync(@"
|
||||
CREATE INDEX IX_RecordAssociations_DestinationEntity
|
||||
ON RecordAssociations (DestinationEntity)");
|
||||
CREATE INDEX IX_KeyAssociations_DestinationEntity
|
||||
ON KeyAssociations (DestinationEntity)");
|
||||
|
||||
await _context.Database.ExecuteSqlRawAsync(@"
|
||||
CREATE INDEX IX_RecordAssociations_RestCredentialName
|
||||
ON RecordAssociations (RestCredentialName)");
|
||||
CREATE INDEX IX_KeyAssociations_RestCredentialName
|
||||
ON KeyAssociations (RestCredentialName)");
|
||||
|
||||
await _context.Database.ExecuteSqlRawAsync(@"
|
||||
CREATE INDEX IX_RecordAssociations_IsActive
|
||||
ON RecordAssociations (IsActive)");
|
||||
CREATE INDEX IX_KeyAssociations_IsActive
|
||||
ON KeyAssociations (IsActive)");
|
||||
|
||||
await _context.Database.ExecuteSqlRawAsync(@"
|
||||
CREATE INDEX IX_RecordAssociations_CreatedAt
|
||||
ON RecordAssociations (CreatedAt)");
|
||||
CREATE INDEX IX_KeyAssociations_CreatedAt
|
||||
ON KeyAssociations (CreatedAt)");
|
||||
|
||||
_logger.LogInformation("Tabella RecordAssociations creata con successo");
|
||||
await _context.Database.ExecuteSqlRawAsync(@"
|
||||
CREATE INDEX IX_KeyAssociations_LastVerifiedAt
|
||||
ON KeyAssociations (LastVerifiedAt)");
|
||||
|
||||
_logger.LogInformation("Tabella KeyAssociations creata con successo");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Errore nella creazione della tabella RecordAssociations");
|
||||
_logger.LogError(ex, "Errore nella creazione della tabella KeyAssociations");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
using CredentialManager.Models;
|
||||
|
||||
namespace CredentialManager.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Interfaccia per il servizio di gestione delle associazioni basate sui valori delle chiavi
|
||||
/// </summary>
|
||||
public interface IKeyAssociationService
|
||||
{
|
||||
/// <summary>
|
||||
/// Salva una nuova associazione o aggiorna una esistente
|
||||
/// </summary>
|
||||
Task<int> SaveAssociationAsync(KeyAssociation association);
|
||||
|
||||
/// <summary>
|
||||
/// Cerca un'associazione esistente tramite valore chiave
|
||||
/// </summary>
|
||||
Task<KeyAssociation?> FindAssociationByKeyValueAsync(string keyValue, string destinationEntity, string restCredentialName);
|
||||
|
||||
/// <summary>
|
||||
/// Cerca un'associazione esistente tramite valore chiave, indipendentemente dalla destinazione
|
||||
/// </summary>
|
||||
Task<KeyAssociation?> FindAssociationByKeyValueAsync(string keyValue);
|
||||
|
||||
/// <summary>
|
||||
/// Ottiene tutte le associazioni per un'entità di destinazione specifica
|
||||
/// </summary>
|
||||
Task<List<KeyAssociation>> GetAssociationsByDestinationAsync(string destinationEntity, string restCredentialName);
|
||||
|
||||
/// <summary>
|
||||
/// Ottiene tutte le associazioni attive
|
||||
/// </summary>
|
||||
Task<List<KeyAssociation>> GetAllActiveAssociationsAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Ottiene tutte le associazioni (attive e non)
|
||||
/// </summary>
|
||||
Task<List<KeyAssociation>> GetAllAssociationsAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Aggiorna un'associazione esistente
|
||||
/// </summary>
|
||||
Task<bool> UpdateAssociationAsync(KeyAssociation association);
|
||||
|
||||
/// <summary>
|
||||
/// Disattiva un'associazione
|
||||
/// </summary>
|
||||
Task<bool> DeactivateAssociationAsync(int id);
|
||||
|
||||
/// <summary>
|
||||
/// Elimina definitivamente un'associazione
|
||||
/// </summary>
|
||||
Task<bool> DeleteAssociationAsync(int id);
|
||||
|
||||
/// <summary>
|
||||
/// Pulisce le associazioni più vecchie di un determinato periodo
|
||||
/// </summary>
|
||||
Task<int> CleanupOldAssociationsAsync(TimeSpan olderThan);
|
||||
|
||||
/// <summary>
|
||||
/// Elimina tutte le associazioni per una specifica combinazione entità-credenziale
|
||||
/// </summary>
|
||||
Task<int> ClearAssociationsAsync(string destinationEntity, string restCredentialName);
|
||||
|
||||
/// <summary>
|
||||
/// Elimina tutte le associazioni nel sistema
|
||||
/// </summary>
|
||||
Task<int> ClearAllAssociationsAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Verifica se un ID di destinazione esiste ancora nel sistema target
|
||||
/// </summary>
|
||||
Task<bool> ValidateDestinationIdAsync(string destinationId, string destinationEntity, string restCredentialName);
|
||||
|
||||
/// <summary>
|
||||
/// Ottiene tutte le associazioni con ID di destinazione non validi
|
||||
/// </summary>
|
||||
Task<List<KeyAssociation>> GetInvalidAssociationsAsync(string destinationEntity, string restCredentialName);
|
||||
|
||||
/// <summary>
|
||||
/// Pulisce le associazioni con ID di destinazione non più validi
|
||||
/// </summary>
|
||||
Task<int> CleanupInvalidAssociationsAsync(string destinationEntity, string restCredentialName);
|
||||
|
||||
/// <summary>
|
||||
/// Aggiorna la data di ultima verifica per un'associazione
|
||||
/// </summary>
|
||||
Task<bool> UpdateLastVerifiedAsync(int id);
|
||||
|
||||
/// <summary>
|
||||
/// Ottiene statistiche sulle associazioni
|
||||
/// </summary>
|
||||
Task<AssociationStatistics> GetStatisticsAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Statistiche sulle associazioni
|
||||
/// </summary>
|
||||
public class AssociationStatistics
|
||||
{
|
||||
public int TotalAssociations { get; set; }
|
||||
public int ActiveAssociations { get; set; }
|
||||
public int InactiveAssociations { get; set; }
|
||||
public int UniqueKeyValues { get; set; }
|
||||
public int UniqueDestinationEntities { get; set; }
|
||||
public DateTime? OldestAssociation { get; set; }
|
||||
public DateTime? NewestAssociation { get; set; }
|
||||
public Dictionary<string, int> AssociationsByEntity { get; set; } = new();
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
using CredentialManager.Models;
|
||||
|
||||
namespace CredentialManager.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Interfaccia per il servizio di gestione delle associazioni record
|
||||
/// </summary>
|
||||
public interface IRecordAssociationService
|
||||
{
|
||||
/// <summary>
|
||||
/// Salva una nuova associazione tra record sorgente e destinazione
|
||||
/// </summary>
|
||||
Task<int> SaveAssociationAsync(RecordAssociation association);
|
||||
|
||||
/// <summary>
|
||||
/// Cerca un'associazione esistente tramite chiave sorgente
|
||||
/// </summary>
|
||||
Task<RecordAssociation?> FindAssociationAsync(string sourceName, string sourceKey, string destinationEntity);
|
||||
|
||||
/// <summary>
|
||||
/// Ottiene tutte le associazioni per una sorgente specifica
|
||||
/// </summary>
|
||||
Task<List<RecordAssociation>> GetAssociationsBySourceAsync(string sourceName, string sourceType);
|
||||
|
||||
/// <summary>
|
||||
/// Ottiene tutte le associazioni per un'entità di destinazione specifica
|
||||
/// </summary>
|
||||
Task<List<RecordAssociation>> GetAssociationsByDestinationAsync(string destinationEntity, string restCredentialName);
|
||||
|
||||
/// <summary>
|
||||
/// Ottiene tutte le associazioni attive
|
||||
/// </summary>
|
||||
Task<List<RecordAssociation>> GetAllActiveAssociationsAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Aggiorna un'associazione esistente
|
||||
/// </summary>
|
||||
Task<bool> UpdateAssociationAsync(RecordAssociation association);
|
||||
|
||||
/// <summary>
|
||||
/// Disattiva un'associazione (soft delete)
|
||||
/// </summary>
|
||||
Task<bool> DeactivateAssociationAsync(int id);
|
||||
|
||||
/// <summary>
|
||||
/// Elimina definitivamente un'associazione
|
||||
/// </summary>
|
||||
Task<bool> DeleteAssociationAsync(int id);
|
||||
|
||||
/// <summary>
|
||||
/// Pulisce le associazioni obsolete (opzionale)
|
||||
/// </summary>
|
||||
Task<int> CleanupOldAssociationsAsync(TimeSpan olderThan);
|
||||
|
||||
/// <summary>
|
||||
/// Elimina tutte le associazioni per una specifica combinazione sorgente-destinazione
|
||||
/// </summary>
|
||||
Task<int> ClearAssociationsAsync(string sourceName, string destinationEntity, string restCredentialName);
|
||||
|
||||
/// <summary>
|
||||
/// Elimina tutte le associazioni nel sistema
|
||||
/// </summary>
|
||||
Task<int> ClearAllAssociationsAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Verifica se un ID di destinazione esiste ancora nel sistema target
|
||||
/// </summary>
|
||||
Task<bool> ValidateDestinationIdAsync(string destinationId, string destinationEntity, string restCredentialName);
|
||||
|
||||
/// <summary>
|
||||
/// Ottiene tutte le associazioni con ID di destinazione non validi
|
||||
/// </summary>
|
||||
Task<List<RecordAssociation>> GetInvalidAssociationsAsync(string destinationEntity, string restCredentialName);
|
||||
|
||||
/// <summary>
|
||||
/// Pulisce le associazioni con ID di destinazione non più validi
|
||||
/// </summary>
|
||||
Task<int> CleanupInvalidAssociationsAsync(string destinationEntity, string restCredentialName);
|
||||
}
|
||||
@@ -0,0 +1,494 @@
|
||||
using CredentialManager.Data;
|
||||
using CredentialManager.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace CredentialManager.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Servizio per la gestione delle associazioni basate sui valori delle chiavi
|
||||
/// </summary>
|
||||
public class KeyAssociationService : IKeyAssociationService
|
||||
{
|
||||
private readonly CredentialDbContext _context;
|
||||
private readonly ILogger<KeyAssociationService> _logger;
|
||||
|
||||
public KeyAssociationService(
|
||||
CredentialDbContext context,
|
||||
ILogger<KeyAssociationService> logger)
|
||||
{
|
||||
_context = context;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<int> SaveAssociationAsync(KeyAssociation association)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("DEBUG: Tentativo salvataggio associazione - KeyValue: '{KeyValue}', DestinationEntity: '{DestinationEntity}', DestinationId: '{DestinationId}', RestCredentialName: '{RestCredentialName}'",
|
||||
association.KeyValue, association.DestinationEntity, association.DestinationId, association.RestCredentialName);
|
||||
|
||||
// Controlla se esiste già un'associazione per questo valore chiave e destinazione
|
||||
var existing = await _context.KeyAssociations
|
||||
.FirstOrDefaultAsync(ka =>
|
||||
ka.KeyValue == association.KeyValue &&
|
||||
ka.DestinationEntity == association.DestinationEntity &&
|
||||
ka.RestCredentialName == association.RestCredentialName &&
|
||||
ka.IsActive);
|
||||
|
||||
_logger.LogInformation("DEBUG: Controllo associazione esistente: {Found}. ID: {Id}",
|
||||
existing != null, existing?.Id);
|
||||
|
||||
if (existing != null)
|
||||
{
|
||||
// Aggiorna l'associazione esistente
|
||||
existing.DestinationId = association.DestinationId;
|
||||
existing.SourceKeyField = association.SourceKeyField;
|
||||
existing.DestinationKeyField = association.DestinationKeyField;
|
||||
existing.UpdatedAt = DateTime.UtcNow;
|
||||
existing.LastVerifiedAt = DateTime.UtcNow;
|
||||
existing.AdditionalInfo = association.AdditionalInfo;
|
||||
|
||||
// Aggiorna le informazioni sulle sorgenti
|
||||
UpdateSourcesInfo(existing, association);
|
||||
|
||||
_context.KeyAssociations.Update(existing);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
_logger.LogInformation("Associazione aggiornata: KeyValue={KeyValue} -> {DestinationEntity}/{DestinationId}",
|
||||
association.KeyValue, association.DestinationEntity, association.DestinationId);
|
||||
|
||||
return existing.Id;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Crea nuova associazione
|
||||
association.CreatedAt = DateTime.UtcNow;
|
||||
association.LastVerifiedAt = DateTime.UtcNow;
|
||||
|
||||
_context.KeyAssociations.Add(association);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
_logger.LogInformation("Nuova associazione creata: KeyValue={KeyValue} -> {DestinationEntity}/{DestinationId}",
|
||||
association.KeyValue, association.DestinationEntity, association.DestinationId);
|
||||
|
||||
return association.Id;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Errore nel salvare l'associazione: KeyValue={KeyValue} -> {DestinationEntity}",
|
||||
association.KeyValue, association.DestinationEntity);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<KeyAssociation?> FindAssociationByKeyValueAsync(string keyValue, string destinationEntity, string restCredentialName)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("DEBUG: Ricerca associazione con parametri - KeyValue: '{KeyValue}', DestinationEntity: '{DestinationEntity}', RestCredentialName: '{RestCredentialName}'",
|
||||
keyValue, destinationEntity, restCredentialName);
|
||||
|
||||
var result = await _context.KeyAssociations
|
||||
.FirstOrDefaultAsync(ka =>
|
||||
ka.KeyValue == keyValue &&
|
||||
ka.DestinationEntity == destinationEntity &&
|
||||
ka.RestCredentialName == restCredentialName &&
|
||||
ka.IsActive);
|
||||
|
||||
_logger.LogInformation("DEBUG: Risultato ricerca associazione: {Found}. ID: {Id}, DestinationId: '{DestinationId}'",
|
||||
result != null, result?.Id, result?.DestinationId);
|
||||
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Errore nella ricerca dell'associazione: KeyValue={KeyValue} -> {DestinationEntity}",
|
||||
keyValue, destinationEntity);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<KeyAssociation?> FindAssociationByKeyValueAsync(string keyValue)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _context.KeyAssociations
|
||||
.Where(ka => ka.KeyValue == keyValue && ka.IsActive)
|
||||
.OrderByDescending(ka => ka.UpdatedAt ?? ka.CreatedAt)
|
||||
.FirstOrDefaultAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Errore nella ricerca dell'associazione per KeyValue={KeyValue}", keyValue);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<KeyAssociation>> GetAssociationsByDestinationAsync(string destinationEntity, string restCredentialName)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _context.KeyAssociations
|
||||
.Where(ka => ka.DestinationEntity == destinationEntity &&
|
||||
ka.RestCredentialName == restCredentialName &&
|
||||
ka.IsActive)
|
||||
.OrderByDescending(ka => ka.CreatedAt)
|
||||
.ToListAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Errore nel recupero delle associazioni per destinazione: {DestinationEntity} ({RestCredentialName})",
|
||||
destinationEntity, restCredentialName);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<KeyAssociation>> GetAllActiveAssociationsAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _context.KeyAssociations
|
||||
.Where(ka => ka.IsActive)
|
||||
.OrderByDescending(ka => ka.CreatedAt)
|
||||
.ToListAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Errore nel recupero di tutte le associazioni attive");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<KeyAssociation>> GetAllAssociationsAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _context.KeyAssociations
|
||||
.OrderByDescending(ka => ka.CreatedAt)
|
||||
.ToListAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Errore nel recupero di tutte le associazioni");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> UpdateAssociationAsync(KeyAssociation association)
|
||||
{
|
||||
try
|
||||
{
|
||||
var existing = await _context.KeyAssociations.FindAsync(association.Id);
|
||||
if (existing == null)
|
||||
{
|
||||
_logger.LogWarning("Associazione con ID {Id} non trovata per l'aggiornamento", association.Id);
|
||||
return false;
|
||||
}
|
||||
|
||||
existing.KeyValue = association.KeyValue;
|
||||
existing.SourceKeyField = association.SourceKeyField;
|
||||
existing.DestinationKeyField = association.DestinationKeyField;
|
||||
existing.DestinationId = association.DestinationId;
|
||||
existing.RestCredentialName = association.RestCredentialName;
|
||||
existing.UpdatedAt = DateTime.UtcNow;
|
||||
existing.AdditionalInfo = association.AdditionalInfo;
|
||||
existing.SourcesInfo = association.SourcesInfo;
|
||||
existing.IsActive = association.IsActive;
|
||||
|
||||
_context.KeyAssociations.Update(existing);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
_logger.LogInformation("Associazione aggiornata: ID {Id}", association.Id);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Errore nell'aggiornamento dell'associazione: ID {Id}", association.Id);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> DeactivateAssociationAsync(int id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var association = await _context.KeyAssociations.FindAsync(id);
|
||||
if (association == null)
|
||||
{
|
||||
_logger.LogWarning("Associazione con ID {Id} non trovata per la disattivazione", id);
|
||||
return false;
|
||||
}
|
||||
|
||||
association.IsActive = false;
|
||||
association.UpdatedAt = DateTime.UtcNow;
|
||||
|
||||
_context.KeyAssociations.Update(association);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
_logger.LogInformation("Associazione disattivata: ID {Id}", id);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Errore nella disattivazione dell'associazione: ID {Id}", id);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteAssociationAsync(int id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var association = await _context.KeyAssociations.FindAsync(id);
|
||||
if (association == null)
|
||||
{
|
||||
_logger.LogWarning("Associazione con ID {Id} non trovata per l'eliminazione", id);
|
||||
return false;
|
||||
}
|
||||
|
||||
_context.KeyAssociations.Remove(association);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
_logger.LogInformation("Associazione eliminata: ID {Id}", id);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Errore nell'eliminazione dell'associazione: ID {Id}", id);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<int> CleanupOldAssociationsAsync(TimeSpan olderThan)
|
||||
{
|
||||
try
|
||||
{
|
||||
var cutoffDate = DateTime.UtcNow - olderThan;
|
||||
var oldAssociations = await _context.KeyAssociations
|
||||
.Where(ka => ka.CreatedAt < cutoffDate && !ka.IsActive)
|
||||
.ToListAsync();
|
||||
|
||||
if (oldAssociations.Any())
|
||||
{
|
||||
_context.KeyAssociations.RemoveRange(oldAssociations);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
_logger.LogInformation("Pulite {Count} associazioni obsolete più vecchie di {Cutoff}",
|
||||
oldAssociations.Count, cutoffDate);
|
||||
}
|
||||
|
||||
return oldAssociations.Count;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Errore nella pulizia delle associazioni obsolete");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<int> ClearAssociationsAsync(string destinationEntity, string restCredentialName)
|
||||
{
|
||||
try
|
||||
{
|
||||
var associationsToDelete = await _context.KeyAssociations
|
||||
.Where(ka => ka.DestinationEntity == destinationEntity &&
|
||||
ka.RestCredentialName == restCredentialName)
|
||||
.ToListAsync();
|
||||
|
||||
if (associationsToDelete.Any())
|
||||
{
|
||||
_context.KeyAssociations.RemoveRange(associationsToDelete);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
_logger.LogInformation("Eliminate {Count} associazioni per {DestinationEntity}",
|
||||
associationsToDelete.Count, destinationEntity);
|
||||
}
|
||||
|
||||
return associationsToDelete.Count;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Errore nella cancellazione delle associazioni per {DestinationEntity}",
|
||||
destinationEntity);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<int> ClearAllAssociationsAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var allAssociations = await _context.KeyAssociations.ToListAsync();
|
||||
var count = allAssociations.Count;
|
||||
|
||||
if (allAssociations.Any())
|
||||
{
|
||||
_context.KeyAssociations.RemoveRange(allAssociations);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
_logger.LogWarning("Eliminate TUTTE le {Count} associazioni dal sistema", count);
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Errore nella cancellazione di tutte le associazioni");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> ValidateDestinationIdAsync(string destinationId, string destinationEntity, string restCredentialName)
|
||||
{
|
||||
// Questa implementazione base restituisce sempre true
|
||||
// Dovrebbe essere estesa per verificare effettivamente l'esistenza nel sistema REST
|
||||
try
|
||||
{
|
||||
// TODO: Implementare la logica di validazione effettiva con il servizio REST
|
||||
// Per ora assumiamo che l'ID sia valido
|
||||
_logger.LogDebug("Validazione ID destinazione {DestinationId} per entità {DestinationEntity} - Non implementata",
|
||||
destinationId, destinationEntity);
|
||||
|
||||
return await Task.FromResult(true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Errore nella validazione dell'ID destinazione {DestinationId}", destinationId);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<KeyAssociation>> GetInvalidAssociationsAsync(string destinationEntity, string restCredentialName)
|
||||
{
|
||||
try
|
||||
{
|
||||
var associations = await _context.KeyAssociations
|
||||
.Where(ka => ka.DestinationEntity == destinationEntity &&
|
||||
ka.RestCredentialName == restCredentialName &&
|
||||
ka.IsActive)
|
||||
.ToListAsync();
|
||||
|
||||
var invalidAssociations = new List<KeyAssociation>();
|
||||
|
||||
// Verifica ogni associazione
|
||||
foreach (var association in associations)
|
||||
{
|
||||
var isValid = await ValidateDestinationIdAsync(association.DestinationId, destinationEntity, restCredentialName);
|
||||
if (!isValid)
|
||||
{
|
||||
invalidAssociations.Add(association);
|
||||
}
|
||||
}
|
||||
|
||||
_logger.LogInformation("Trovate {Invalid}/{Total} associazioni non valide per {DestinationEntity}",
|
||||
invalidAssociations.Count, associations.Count, destinationEntity);
|
||||
|
||||
return invalidAssociations;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Errore nel recupero delle associazioni non valide per {DestinationEntity}", destinationEntity);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<int> CleanupInvalidAssociationsAsync(string destinationEntity, string restCredentialName)
|
||||
{
|
||||
try
|
||||
{
|
||||
var invalidAssociations = await GetInvalidAssociationsAsync(destinationEntity, restCredentialName);
|
||||
|
||||
if (invalidAssociations.Any())
|
||||
{
|
||||
_context.KeyAssociations.RemoveRange(invalidAssociations);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
_logger.LogWarning("Eliminate {Count} associazioni non valide per {DestinationEntity}",
|
||||
invalidAssociations.Count, destinationEntity);
|
||||
}
|
||||
|
||||
return invalidAssociations.Count;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Errore nella pulizia delle associazioni non valide per {DestinationEntity}", destinationEntity);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> UpdateLastVerifiedAsync(int id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var association = await _context.KeyAssociations.FindAsync(id);
|
||||
if (association == null)
|
||||
{
|
||||
_logger.LogWarning("Associazione con ID {Id} non trovata per l'aggiornamento della verifica", id);
|
||||
return false;
|
||||
}
|
||||
|
||||
association.LastVerifiedAt = DateTime.UtcNow;
|
||||
_context.KeyAssociations.Update(association);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Errore nell'aggiornamento della verifica per associazione: ID {Id}", id);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<AssociationStatistics> GetStatisticsAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var allAssociations = await _context.KeyAssociations.ToListAsync();
|
||||
|
||||
var stats = new AssociationStatistics
|
||||
{
|
||||
TotalAssociations = allAssociations.Count,
|
||||
ActiveAssociations = allAssociations.Count(a => a.IsActive),
|
||||
InactiveAssociations = allAssociations.Count(a => !a.IsActive),
|
||||
UniqueKeyValues = allAssociations.Select(a => a.KeyValue).Distinct().Count(),
|
||||
UniqueDestinationEntities = allAssociations.Select(a => a.DestinationEntity).Distinct().Count(),
|
||||
OldestAssociation = allAssociations.Any() ? allAssociations.Min(a => a.CreatedAt) : null,
|
||||
NewestAssociation = allAssociations.Any() ? allAssociations.Max(a => a.CreatedAt) : null,
|
||||
AssociationsByEntity = allAssociations
|
||||
.GroupBy(a => a.DestinationEntity)
|
||||
.ToDictionary(g => g.Key, g => g.Count())
|
||||
};
|
||||
|
||||
return stats;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Errore nel calcolo delle statistiche delle associazioni");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateSourcesInfo(KeyAssociation existing, KeyAssociation newAssociation)
|
||||
{
|
||||
try
|
||||
{
|
||||
var sourcesInfo = existing.SourcesInfo ?? "";
|
||||
var timestamp = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm");
|
||||
var newSourceInfo = $"{timestamp}: {newAssociation.SourceKeyField}";
|
||||
|
||||
if (!sourcesInfo.Contains(newSourceInfo))
|
||||
{
|
||||
existing.SourcesInfo = string.IsNullOrEmpty(sourcesInfo)
|
||||
? newSourceInfo
|
||||
: $"{sourcesInfo}; {newSourceInfo}";
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Errore nell'aggiornamento delle informazioni sulle sorgenti");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,381 +0,0 @@
|
||||
using CredentialManager.Data;
|
||||
using CredentialManager.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace CredentialManager.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Servizio per la gestione delle associazioni tra record sorgente e destinazione
|
||||
/// </summary>
|
||||
public class RecordAssociationService : IRecordAssociationService
|
||||
{
|
||||
private readonly CredentialDbContext _context;
|
||||
private readonly ILogger<RecordAssociationService> _logger;
|
||||
|
||||
public RecordAssociationService(
|
||||
CredentialDbContext context,
|
||||
ILogger<RecordAssociationService> logger)
|
||||
{
|
||||
_context = context;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<int> SaveAssociationAsync(RecordAssociation association)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Controlla se esiste già un'associazione per questa combinazione
|
||||
var existing = await _context.RecordAssociations
|
||||
.FirstOrDefaultAsync(ra =>
|
||||
ra.SourceName == association.SourceName &&
|
||||
ra.SourceKey == association.SourceKey &&
|
||||
ra.DestinationEntity == association.DestinationEntity &&
|
||||
ra.IsActive);
|
||||
|
||||
if (existing != null)
|
||||
{
|
||||
// Aggiorna l'associazione esistente
|
||||
existing.DestinationId = association.DestinationId;
|
||||
existing.RestCredentialName = association.RestCredentialName;
|
||||
existing.UpdatedAt = DateTime.UtcNow;
|
||||
existing.AdditionalInfo = association.AdditionalInfo;
|
||||
|
||||
_context.RecordAssociations.Update(existing);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
_logger.LogInformation("Associazione aggiornata: {SourceName}/{SourceKey} -> {DestinationEntity}/{DestinationId}",
|
||||
association.SourceName, association.SourceKey, association.DestinationEntity, association.DestinationId);
|
||||
|
||||
return existing.Id;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Crea nuova associazione
|
||||
_context.RecordAssociations.Add(association);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
_logger.LogInformation("Nuova associazione creata: {SourceName}/{SourceKey} -> {DestinationEntity}/{DestinationId}",
|
||||
association.SourceName, association.SourceKey, association.DestinationEntity, association.DestinationId);
|
||||
|
||||
return association.Id;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Errore nel salvare l'associazione: {SourceName}/{SourceKey} -> {DestinationEntity}",
|
||||
association.SourceName, association.SourceKey, association.DestinationEntity);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<RecordAssociation?> FindAssociationAsync(string sourceName, string sourceKey, string destinationEntity)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _context.RecordAssociations
|
||||
.FirstOrDefaultAsync(ra =>
|
||||
ra.SourceName == sourceName &&
|
||||
ra.SourceKey == sourceKey &&
|
||||
ra.DestinationEntity == destinationEntity &&
|
||||
ra.IsActive);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Errore nella ricerca dell'associazione: {SourceName}/{SourceKey} -> {DestinationEntity}",
|
||||
sourceName, sourceKey, destinationEntity);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<RecordAssociation>> GetAssociationsBySourceAsync(string sourceName, string sourceType)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _context.RecordAssociations
|
||||
.Where(ra => ra.SourceName == sourceName && ra.SourceType == sourceType && ra.IsActive)
|
||||
.OrderByDescending(ra => ra.CreatedAt)
|
||||
.ToListAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Errore nel recupero delle associazioni per sorgente: {SourceName} ({SourceType})",
|
||||
sourceName, sourceType);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<RecordAssociation>> GetAssociationsByDestinationAsync(string destinationEntity, string restCredentialName)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _context.RecordAssociations
|
||||
.Where(ra => ra.DestinationEntity == destinationEntity &&
|
||||
ra.RestCredentialName == restCredentialName &&
|
||||
ra.IsActive)
|
||||
.OrderByDescending(ra => ra.CreatedAt)
|
||||
.ToListAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Errore nel recupero delle associazioni per destinazione: {DestinationEntity} ({RestCredentialName})",
|
||||
destinationEntity, restCredentialName);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<RecordAssociation>> GetAllActiveAssociationsAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _context.RecordAssociations
|
||||
.Where(ra => ra.IsActive)
|
||||
.OrderByDescending(ra => ra.CreatedAt)
|
||||
.ToListAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Errore nel recupero di tutte le associazioni attive");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> UpdateAssociationAsync(RecordAssociation association)
|
||||
{
|
||||
try
|
||||
{
|
||||
var existing = await _context.RecordAssociations.FindAsync(association.Id);
|
||||
if (existing == null)
|
||||
{
|
||||
_logger.LogWarning("Associazione con ID {Id} non trovata per l'aggiornamento", association.Id);
|
||||
return false;
|
||||
}
|
||||
|
||||
existing.DestinationId = association.DestinationId;
|
||||
existing.RestCredentialName = association.RestCredentialName;
|
||||
existing.UpdatedAt = DateTime.UtcNow;
|
||||
existing.AdditionalInfo = association.AdditionalInfo;
|
||||
existing.IsActive = association.IsActive;
|
||||
|
||||
_context.RecordAssociations.Update(existing);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
_logger.LogInformation("Associazione aggiornata: ID {Id}", association.Id);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Errore nell'aggiornamento dell'associazione: ID {Id}", association.Id);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> DeactivateAssociationAsync(int id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var association = await _context.RecordAssociations.FindAsync(id);
|
||||
if (association == null)
|
||||
{
|
||||
_logger.LogWarning("Associazione con ID {Id} non trovata per la disattivazione", id);
|
||||
return false;
|
||||
}
|
||||
|
||||
association.IsActive = false;
|
||||
association.UpdatedAt = DateTime.UtcNow;
|
||||
|
||||
_context.RecordAssociations.Update(association);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
_logger.LogInformation("Associazione disattivata: ID {Id}", id);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Errore nella disattivazione dell'associazione: ID {Id}", id);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteAssociationAsync(int id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var association = await _context.RecordAssociations.FindAsync(id);
|
||||
if (association == null)
|
||||
{
|
||||
_logger.LogWarning("Associazione con ID {Id} non trovata per l'eliminazione", id);
|
||||
return false;
|
||||
}
|
||||
|
||||
_context.RecordAssociations.Remove(association);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
_logger.LogInformation("Associazione eliminata: ID {Id}", id);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Errore nell'eliminazione dell'associazione: ID {Id}", id);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<int> CleanupOldAssociationsAsync(TimeSpan olderThan)
|
||||
{
|
||||
try
|
||||
{
|
||||
var cutoffDate = DateTime.UtcNow - olderThan;
|
||||
var oldAssociations = await _context.RecordAssociations
|
||||
.Where(ra => ra.CreatedAt < cutoffDate && !ra.IsActive)
|
||||
.ToListAsync();
|
||||
|
||||
if (oldAssociations.Any())
|
||||
{
|
||||
_context.RecordAssociations.RemoveRange(oldAssociations);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
_logger.LogInformation("Pulite {Count} associazioni obsolete più vecchie di {Cutoff}",
|
||||
oldAssociations.Count, cutoffDate);
|
||||
}
|
||||
|
||||
return oldAssociations.Count;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Errore nella pulizia delle associazioni obsolete");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<int> ClearAssociationsAsync(string sourceName, string destinationEntity, string restCredentialName)
|
||||
{
|
||||
try
|
||||
{
|
||||
var associationsToDelete = await _context.RecordAssociations
|
||||
.Where(ra => ra.SourceName == sourceName &&
|
||||
ra.DestinationEntity == destinationEntity &&
|
||||
ra.RestCredentialName == restCredentialName)
|
||||
.ToListAsync();
|
||||
|
||||
if (associationsToDelete.Any())
|
||||
{
|
||||
_context.RecordAssociations.RemoveRange(associationsToDelete);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
_logger.LogInformation("Eliminate {Count} associazioni per {SourceName} -> {DestinationEntity}",
|
||||
associationsToDelete.Count, sourceName, destinationEntity);
|
||||
}
|
||||
|
||||
return associationsToDelete.Count;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Errore nella cancellazione delle associazioni per {SourceName} -> {DestinationEntity}",
|
||||
sourceName, destinationEntity);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<int> ClearAllAssociationsAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var allAssociations = await _context.RecordAssociations.ToListAsync();
|
||||
var count = allAssociations.Count;
|
||||
|
||||
if (allAssociations.Any())
|
||||
{
|
||||
_context.RecordAssociations.RemoveRange(allAssociations);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
_logger.LogWarning("Eliminate TUTTE le {Count} associazioni dal sistema", count);
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Errore nella cancellazione di tutte le associazioni");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> ValidateDestinationIdAsync(string destinationId, string destinationEntity, string restCredentialName)
|
||||
{
|
||||
// Questa implementazione base restituisce sempre true
|
||||
// Dovrebbe essere estesa per verificare effettivamente l'esistenza nel sistema REST
|
||||
try
|
||||
{
|
||||
// TODO: Implementare la logica di validazione effettiva con il servizio REST
|
||||
// Per ora assumiamo che l'ID sia valido
|
||||
_logger.LogDebug("Validazione ID destinazione {DestinationId} per entità {DestinationEntity} - Non implementata",
|
||||
destinationId, destinationEntity);
|
||||
|
||||
return await Task.FromResult(true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Errore nella validazione dell'ID destinazione {DestinationId}", destinationId);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<RecordAssociation>> GetInvalidAssociationsAsync(string destinationEntity, string restCredentialName)
|
||||
{
|
||||
try
|
||||
{
|
||||
var associations = await _context.RecordAssociations
|
||||
.Where(ra => ra.DestinationEntity == destinationEntity &&
|
||||
ra.RestCredentialName == restCredentialName &&
|
||||
ra.IsActive)
|
||||
.ToListAsync();
|
||||
|
||||
var invalidAssociations = new List<RecordAssociation>();
|
||||
|
||||
// Verifica ogni associazione
|
||||
foreach (var association in associations)
|
||||
{
|
||||
var isValid = await ValidateDestinationIdAsync(association.DestinationId, destinationEntity, restCredentialName);
|
||||
if (!isValid)
|
||||
{
|
||||
invalidAssociations.Add(association);
|
||||
}
|
||||
}
|
||||
|
||||
_logger.LogInformation("Trovate {Invalid}/{Total} associazioni non valide per {DestinationEntity}",
|
||||
invalidAssociations.Count, associations.Count, destinationEntity);
|
||||
|
||||
return invalidAssociations;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Errore nel recupero delle associazioni non valide per {DestinationEntity}", destinationEntity);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<int> CleanupInvalidAssociationsAsync(string destinationEntity, string restCredentialName)
|
||||
{
|
||||
try
|
||||
{
|
||||
var invalidAssociations = await GetInvalidAssociationsAsync(destinationEntity, restCredentialName);
|
||||
|
||||
if (invalidAssociations.Any())
|
||||
{
|
||||
_context.RecordAssociations.RemoveRange(invalidAssociations);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
_logger.LogWarning("Eliminate {Count} associazioni non valide per {DestinationEntity}",
|
||||
invalidAssociations.Count, destinationEntity);
|
||||
}
|
||||
|
||||
return invalidAssociations.Count;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Errore nella pulizia delle associazioni non valide per {DestinationEntity}", destinationEntity);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user