Implementato sistema robusto di salvataggio/caricamento profili Data Coupler

- Aggiunto metodo GetCredentialIdByNameAsync in CredentialService per recuperare ID credenziali per nome
- Implementata gestione robusta dei profili duplicati con riattivazione, sovrascrittura e auto-rinomina
- Migliorata logica di caricamento profili con simulazione workflow utente e logging dettagliato
- Fixata gestione errori UNIQUE constraint nel salvataggio profili
- Aggiunto supporto per salvataggio ID credenziali reali invece di placeholder
- Implementato metodo GetProfileByNameIncludingInactiveAsync per gestire profili inattivi
- Aggiunto logging esteso per debug e troubleshooting
- Integrato componente ProfileSaver nella UI principale
- Risolti errori di compilazione e validazione build completa
- Migliorata gestione errori con feedback utente per credenziali/entità mancanti
This commit is contained in:
Alessio Dal Santo
2025-07-03 16:30:57 +02:00
parent d837339f7e
commit 65ed2bb93a
19 changed files with 967 additions and 115 deletions
@@ -39,6 +39,9 @@ public interface ICredentialService
Task<bool> DeleteCredentialAsync(int id);
Task<bool> DeleteCredentialAsync(string name);
Task<List<string>> GetCredentialNamesAsync(CredentialType? type = null);
// Helper methods to get credential ID by name
Task<int?> GetCredentialIdByNameAsync(string name, CredentialType type);
}
/// <summary>
@@ -960,5 +963,27 @@ public class CredentialService : ICredentialService
credentialValue.Contains("*** ERRORE DECRITTOGRAFIA ***");
}
/// <summary>
/// Ottiene l'ID di una credenziale per nome e tipo
/// </summary>
/// <param name="name">Nome della credenziale</param>
/// <param name="type">Tipo della credenziale</param>
/// <returns>ID della credenziale se trovata, null altrimenti</returns>
public async Task<int?> GetCredentialIdByNameAsync(string name, CredentialType type)
{
try
{
var entity = await _context.Credentials
.FirstOrDefaultAsync(c => c.Name == name && c.Type == type.ToString() && c.IsActive);
return entity?.Id;
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore nel recuperare l'ID della credenziale: {Name}, Tipo: {Type}", name, type);
return null;
}
}
#endregion
}
@@ -31,6 +31,17 @@ public class DataCouplerProfileService : IDataCouplerProfileService
.ToListAsync();
}
/// <summary>
/// Ottiene tutti i profili per nome (inclusi quelli inattivi)
/// </summary>
public async Task<DataCouplerProfile?> GetProfileByNameIncludingInactiveAsync(string name)
{
return await _context.DataCouplerProfiles
.Include(p => p.SourceCredential)
.Include(p => p.DestinationCredential)
.FirstOrDefaultAsync(p => p.Name.ToLower() == name.ToLower());
}
/// <summary>
/// Ottiene un profilo per ID
/// </summary>
@@ -80,8 +91,12 @@ public class DataCouplerProfileService : IDataCouplerProfileService
throw new InvalidOperationException($"Profilo con ID {profile.Id} non trovato");
}
// Aggiorna le proprietà
existingProfile.Name = profile.Name;
// Aggiorna le proprietà (evita di aggiornare il nome se è uguale per evitare unique constraint)
if (!string.Equals(existingProfile.Name, profile.Name, StringComparison.OrdinalIgnoreCase))
{
existingProfile.Name = profile.Name;
}
existingProfile.Description = profile.Description;
existingProfile.SourceType = profile.SourceType;
existingProfile.SourceCredentialId = profile.SourceCredentialId;
@@ -94,6 +109,9 @@ public class DataCouplerProfileService : IDataCouplerProfileService
existingProfile.DestinationTable = profile.DestinationTable;
existingProfile.DestinationEndpoint = profile.DestinationEndpoint;
existingProfile.FieldMappingJson = profile.FieldMappingJson;
existingProfile.SourceKeyField = profile.SourceKeyField;
existingProfile.UseRecordAssociations = profile.UseRecordAssociations;
existingProfile.IsActive = profile.IsActive;
await _context.SaveChangesAsync();
return existingProfile;
@@ -195,15 +213,19 @@ public class DataCouplerProfileService : IDataCouplerProfileService
Description = profile.Description,
SourceType = profile.SourceType,
SourceCredentialId = profile.SourceCredentialId,
SourceCredentialName = profile.SourceCredential?.Name,
SourceSchema = profile.SourceSchema,
SourceTable = profile.SourceTable,
SourceFilePath = profile.SourceFilePath,
DestinationType = profile.DestinationType,
DestinationCredentialId = profile.DestinationCredentialId,
DestinationCredentialName = profile.DestinationCredential?.Name,
DestinationSchema = profile.DestinationSchema,
DestinationTable = profile.DestinationTable,
DestinationEndpoint = profile.DestinationEndpoint,
FieldMappings = DeserializeFieldMappings(profile.FieldMappingJson)
FieldMappings = DeserializeFieldMappings(profile.FieldMappingJson),
SourceKeyField = profile.SourceKeyField,
UseRecordAssociations = profile.UseRecordAssociations
};
}
@@ -228,6 +250,8 @@ public class DataCouplerProfileService : IDataCouplerProfileService
DestinationTable = dto.DestinationTable,
DestinationEndpoint = dto.DestinationEndpoint,
FieldMappingJson = SerializeFieldMappings(dto.FieldMappings),
SourceKeyField = dto.SourceKeyField,
UseRecordAssociations = dto.UseRecordAssociations,
CreatedBy = createdBy
};
}
@@ -12,6 +12,11 @@ public interface IDataCouplerProfileService
/// </summary>
Task<IEnumerable<DataCouplerProfile>> GetAllProfilesAsync();
/// <summary>
/// Ottiene tutti i profili per nome (inclusi quelli inattivi)
/// </summary>
Task<DataCouplerProfile?> GetProfileByNameIncludingInactiveAsync(string name);
/// <summary>
/// Ottiene un profilo per ID
/// </summary>