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:
2025-06-29 20:44:20 +02:00
parent 2238ddc4bf
commit 04f0403f12
23 changed files with 2051 additions and 1161 deletions
@@ -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;
}
}