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
@@ -1,4 +1,5 @@
using CredentialManager.Models;
using CredentialManager.Services;
namespace DataConnection.CredentialManagement.Interfaces;
@@ -57,17 +58,20 @@ public interface IDataConnectionCredentialService
Task<(bool Success, string Message)> TestSalesforceConnectionAsync(string credentialName);
Task<(bool Success, string Message)> TestSalesforceConnectionAsync(SalesforceCredential credential);
// Record associations
Task<int> SaveRecordAssociationAsync(RecordAssociation association);
Task<RecordAssociation?> FindRecordAssociationAsync(string sourceName, string sourceKey, string destinationEntity);
Task<List<RecordAssociation>> GetRecordAssociationsBySourceAsync(string sourceName, string sourceType);
Task<List<RecordAssociation>> GetRecordAssociationsByDestinationAsync(string destinationEntity, string restCredentialName);
Task<List<RecordAssociation>> GetAllActiveRecordAssociationsAsync();
Task<bool> UpdateRecordAssociationAsync(RecordAssociation association);
Task<bool> DeactivateRecordAssociationAsync(int id);
Task<bool> DeleteRecordAssociationAsync(int id);
Task<int> ClearRecordAssociationsAsync(string sourceName, string destinationEntity, string restCredentialName);
Task<int> ClearAllRecordAssociationsAsync();
Task<List<RecordAssociation>> GetInvalidRecordAssociationsAsync(string destinationEntity, string restCredentialName);
Task<int> CleanupInvalidRecordAssociationsAsync(string destinationEntity, string restCredentialName);
// Key associations
Task<int> SaveKeyAssociationAsync(KeyAssociation association);
Task<KeyAssociation?> FindKeyAssociationByValueAsync(string keyValue, string destinationEntity, string restCredentialName);
Task<KeyAssociation?> FindKeyAssociationByValueAsync(string keyValue);
Task<List<KeyAssociation>> GetKeyAssociationsByDestinationAsync(string destinationEntity, string restCredentialName);
Task<List<KeyAssociation>> GetAllActiveKeyAssociationsAsync();
Task<List<KeyAssociation>> GetAllKeyAssociationsAsync();
Task<bool> UpdateKeyAssociationAsync(KeyAssociation association);
Task<bool> DeactivateKeyAssociationAsync(int id);
Task<bool> DeleteKeyAssociationAsync(int id);
Task<int> ClearKeyAssociationsAsync(string destinationEntity, string restCredentialName);
Task<int> ClearAllKeyAssociationsAsync();
Task<List<KeyAssociation>> GetInvalidKeyAssociationsAsync(string destinationEntity, string restCredentialName);
Task<int> CleanupInvalidKeyAssociationsAsync(string destinationEntity, string restCredentialName);
Task<bool> UpdateKeyAssociationLastVerifiedAsync(int id);
Task<AssociationStatistics> GetKeyAssociationStatisticsAsync();
}
@@ -38,8 +38,8 @@ public static class ServiceCollectionExtensions
// Aggiungi i servizi base di CredentialManager
services.AddCredentialManager(databasePath);
// Aggiungi il servizio di gestione associazioni record
services.AddScoped<IRecordAssociationService, RecordAssociationService>();
// Aggiungi il servizio di gestione associazioni per chiavi
services.AddScoped<IKeyAssociationService, KeyAssociationService>();
// Aggiungi il servizio di integrazione DataConnection
services.AddScoped<IDataConnectionCredentialService, DataConnectionCredentialService>();
@@ -15,16 +15,16 @@ namespace DataConnection.CredentialManagement.Services;
public class DataConnectionCredentialService : IDataConnectionCredentialService
{
private readonly ICredentialService _credentialService;
private readonly IRecordAssociationService _recordAssociationService;
private readonly IKeyAssociationService _keyAssociationService;
private readonly ILogger<DataConnectionCredentialService> _logger;
public DataConnectionCredentialService(
ICredentialService credentialService,
IRecordAssociationService recordAssociationService,
IKeyAssociationService keyAssociationService,
ILogger<DataConnectionCredentialService> logger)
{
_credentialService = credentialService;
_recordAssociationService = recordAssociationService;
_keyAssociationService = keyAssociationService;
_logger = logger;
}
@@ -859,66 +859,81 @@ public class DataConnectionCredentialService : IDataConnectionCredentialService
#endregion
#region Record Associations
#region Key Associations
public async Task<int> SaveRecordAssociationAsync(RecordAssociation association)
public async Task<int> SaveKeyAssociationAsync(KeyAssociation association)
{
return await _recordAssociationService.SaveAssociationAsync(association);
return await _keyAssociationService.SaveAssociationAsync(association);
}
public async Task<RecordAssociation?> FindRecordAssociationAsync(string sourceName, string sourceKey, string destinationEntity)
public async Task<KeyAssociation?> FindKeyAssociationByValueAsync(string keyValue, string destinationEntity, string restCredentialName)
{
return await _recordAssociationService.FindAssociationAsync(sourceName, sourceKey, destinationEntity);
return await _keyAssociationService.FindAssociationByKeyValueAsync(keyValue, destinationEntity, restCredentialName);
}
public async Task<List<RecordAssociation>> GetRecordAssociationsBySourceAsync(string sourceName, string sourceType)
public async Task<KeyAssociation?> FindKeyAssociationByValueAsync(string keyValue)
{
return await _recordAssociationService.GetAssociationsBySourceAsync(sourceName, sourceType);
return await _keyAssociationService.FindAssociationByKeyValueAsync(keyValue);
}
public async Task<List<RecordAssociation>> GetRecordAssociationsByDestinationAsync(string destinationEntity, string restCredentialName)
public async Task<List<KeyAssociation>> GetKeyAssociationsByDestinationAsync(string destinationEntity, string restCredentialName)
{
return await _recordAssociationService.GetAssociationsByDestinationAsync(destinationEntity, restCredentialName);
return await _keyAssociationService.GetAssociationsByDestinationAsync(destinationEntity, restCredentialName);
}
public async Task<List<RecordAssociation>> GetAllActiveRecordAssociationsAsync()
public async Task<List<KeyAssociation>> GetAllActiveKeyAssociationsAsync()
{
return await _recordAssociationService.GetAllActiveAssociationsAsync();
return await _keyAssociationService.GetAllActiveAssociationsAsync();
}
public async Task<bool> UpdateRecordAssociationAsync(RecordAssociation association)
public async Task<List<KeyAssociation>> GetAllKeyAssociationsAsync()
{
return await _recordAssociationService.UpdateAssociationAsync(association);
return await _keyAssociationService.GetAllAssociationsAsync();
}
public async Task<bool> DeactivateRecordAssociationAsync(int id)
public async Task<bool> UpdateKeyAssociationAsync(KeyAssociation association)
{
return await _recordAssociationService.DeactivateAssociationAsync(id);
return await _keyAssociationService.UpdateAssociationAsync(association);
}
public async Task<bool> DeleteRecordAssociationAsync(int id)
public async Task<bool> DeactivateKeyAssociationAsync(int id)
{
return await _recordAssociationService.DeleteAssociationAsync(id);
return await _keyAssociationService.DeactivateAssociationAsync(id);
}
public async Task<int> ClearRecordAssociationsAsync(string sourceName, string destinationEntity, string restCredentialName)
public async Task<bool> DeleteKeyAssociationAsync(int id)
{
return await _recordAssociationService.ClearAssociationsAsync(sourceName, destinationEntity, restCredentialName);
return await _keyAssociationService.DeleteAssociationAsync(id);
}
public async Task<int> ClearAllRecordAssociationsAsync()
public async Task<int> ClearKeyAssociationsAsync(string destinationEntity, string restCredentialName)
{
return await _recordAssociationService.ClearAllAssociationsAsync();
return await _keyAssociationService.ClearAssociationsAsync(destinationEntity, restCredentialName);
}
public async Task<List<RecordAssociation>> GetInvalidRecordAssociationsAsync(string destinationEntity, string restCredentialName)
public async Task<int> ClearAllKeyAssociationsAsync()
{
return await _recordAssociationService.GetInvalidAssociationsAsync(destinationEntity, restCredentialName);
return await _keyAssociationService.ClearAllAssociationsAsync();
}
public async Task<int> CleanupInvalidRecordAssociationsAsync(string destinationEntity, string restCredentialName)
public async Task<List<KeyAssociation>> GetInvalidKeyAssociationsAsync(string destinationEntity, string restCredentialName)
{
return await _recordAssociationService.CleanupInvalidAssociationsAsync(destinationEntity, restCredentialName);
return await _keyAssociationService.GetInvalidAssociationsAsync(destinationEntity, restCredentialName);
}
public async Task<int> CleanupInvalidKeyAssociationsAsync(string destinationEntity, string restCredentialName)
{
return await _keyAssociationService.CleanupInvalidAssociationsAsync(destinationEntity, restCredentialName);
}
public async Task<bool> UpdateKeyAssociationLastVerifiedAsync(int id)
{
return await _keyAssociationService.UpdateLastVerifiedAsync(id);
}
public async Task<AssociationStatistics> GetKeyAssociationStatisticsAsync()
{
return await _keyAssociationService.GetStatisticsAsync();
}
#endregion