Files
Data-Coupler/Data_Coupler/Services/DataTransferService.cs
T
Alessio Dal Santo a5f8943c72
Build and Push Docker Images / Build Linux Container (push) Successful in 8m59s
Build and Push Docker Images / Build Windows Container (push) Successful in 9m35s
Build and Push Docker Images / Create Multi-Platform Manifest (push) Failing after 25s
[Feature] Implementata schedulazione completa per file CSV/Excel
- Aggiunta validazione percorsi file prima del salvataggio profili
- Implementati metodi di lettura file CSV e Excel per schedulazioni
- Supporto doppia modalità: caricamento browser (preview) e percorso manuale (schedulazione)
- Gestione completa deletion sync anche per file CSV/Excel
- Rilevamento automatico separatori CSV (virgola, punto e virgola, tab, pipe)
- Supporto formati Excel legacy (.xls) e moderni (.xlsx)
- Abilitati profili file nella UI di schedulazione
- Logging dettagliato per troubleshooting
- Documentazione completa in CSV_SCHEDULING_IMPLEMENTATION.md
- Aggiornati README.md e copilot-instructions.md con nuove feature
- Rimosso testo 'TEST' dalla pagina di login
2026-01-25 12:45:32 +01:00

201 lines
8.7 KiB
C#

using CredentialManager.Models;
using Microsoft.Extensions.Logging;
namespace Data_Coupler.Services;
/// <summary>
/// Servizio per l'esecuzione automatica dei trasferimenti dati basati sui profili
/// </summary>
public interface IDataTransferService
{
Task<DataTransferResult> ExecuteProfileAsync(DataCouplerProfile profile, string? sourceDatabaseOverride = null, string? destinationDatabaseOverride = null, bool enableDeletionSync = false);
}
public class DataTransferResult
{
public bool IsSuccess { get; set; }
public int RecordsProcessed { get; set; }
public string? ErrorMessage { get; set; }
public DateTime StartTime { get; set; }
public DateTime EndTime { get; set; }
public List<string> ErrorDetails { get; set; } = new();
public Dictionary<string, object> AdditionalInfo { get; set; } = new();
public TimeSpan Duration => EndTime - StartTime;
}
public class DataTransferService : IDataTransferService
{
private readonly IScheduledProfileExecutionService _scheduledExecutionService;
private readonly ILogger<DataTransferService> _logger;
public DataTransferService(
IScheduledProfileExecutionService scheduledExecutionService,
ILogger<DataTransferService> logger)
{
_scheduledExecutionService = scheduledExecutionService ?? throw new ArgumentNullException(nameof(scheduledExecutionService));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public async Task<DataTransferResult> ExecuteProfileAsync(DataCouplerProfile profile, string? sourceDatabaseOverride = null, string? destinationDatabaseOverride = null, bool enableDeletionSync = false)
{
var result = new DataTransferResult
{
StartTime = DateTime.Now // Usa l'ora locale per coerenza con le schedulazioni
};
try
{
_logger.LogInformation("Iniziando esecuzione profilo {ProfileName} (ID: {ProfileId})",
profile.Name, profile.Id);
// Validazione del profilo
var validationResult = ValidateProfile(profile);
if (!validationResult.IsValid)
{
result.IsSuccess = false;
result.ErrorMessage = validationResult.ErrorMessage;
result.EndTime = DateTime.Now; // Usa l'ora locale per coerenza
return result;
}
// Applica override del database se specificati
var profileToExecute = await ApplyDatabaseOverrides(profile, sourceDatabaseOverride, destinationDatabaseOverride);
// Utilizza il servizio esistente per l'esecuzione
var executionResult = await _scheduledExecutionService.ExecuteProfileAsync(profileToExecute.Id, enableDeletionSync);
result.IsSuccess = executionResult.Success;
result.RecordsProcessed = executionResult.RecordsProcessed;
result.ErrorMessage = executionResult.Success ? null : executionResult.Message;
result.EndTime = DateTime.Now; // Usa l'ora locale per coerenza
if (executionResult.Success)
{
result.AdditionalInfo["ExecutionDuration"] = executionResult.Duration.ToString();
_logger.LogInformation("Profilo {ProfileName} eseguito con successo. " +
"Record processati: {RecordsProcessed}, Durata: {Duration}ms",
profile.Name, result.RecordsProcessed, result.Duration.TotalMilliseconds);
}
else
{
result.ErrorDetails.Add(executionResult.Message);
_logger.LogError("Errore nell'esecuzione del profilo {ProfileName}: {ErrorMessage}",
profile.Name, executionResult.Message);
}
return result;
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore durante l'esecuzione del profilo {ProfileName} (ID: {ProfileId})",
profile.Name, profile.Id);
result.IsSuccess = false;
result.ErrorMessage = ex.Message;
result.ErrorDetails.Add($"Exception: {ex.GetType().Name} - {ex.Message}");
if (ex.InnerException != null)
{
result.ErrorDetails.Add($"Inner Exception: {ex.InnerException.Message}");
}
result.EndTime = DateTime.Now; // Usa l'ora locale per coerenza
return result;
}
}
private async Task<DataCouplerProfile> ApplyDatabaseOverrides(DataCouplerProfile originalProfile,
string? sourceDatabaseOverride, string? destinationDatabaseOverride)
{
// Se non ci sono override, restituisce il profilo originale
if (string.IsNullOrEmpty(sourceDatabaseOverride) && string.IsNullOrEmpty(destinationDatabaseOverride))
{
return originalProfile;
}
// Crea una copia del profilo con gli override applicati
// In un'implementazione reale, potresti voler creare una copia più sofisticata
// o utilizzare un metodo di clonazione appropriato
var profileCopy = new DataCouplerProfile
{
Id = originalProfile.Id,
Name = originalProfile.Name,
Description = originalProfile.Description,
SourceType = originalProfile.SourceType,
SourceCredentialId = originalProfile.SourceCredentialId,
SourceDatabaseName = originalProfile.SourceDatabaseName,
SourceTable = originalProfile.SourceTable,
SourceSchema = originalProfile.SourceSchema,
SourceCustomQuery = originalProfile.SourceCustomQuery,
SourceFilePath = originalProfile.SourceFilePath,
DestinationType = originalProfile.DestinationType,
DestinationCredentialId = originalProfile.DestinationCredentialId,
DestinationTable = originalProfile.DestinationTable,
DestinationSchema = originalProfile.DestinationSchema,
DestinationEndpoint = originalProfile.DestinationEndpoint,
FieldMappingJson = originalProfile.FieldMappingJson,
SourceKeyField = originalProfile.SourceKeyField,
UseRecordAssociations = originalProfile.UseRecordAssociations,
IsActive = originalProfile.IsActive,
CreatedAt = originalProfile.CreatedAt,
LastUsedAt = originalProfile.LastUsedAt,
CreatedBy = originalProfile.CreatedBy
};
// TODO: Implementare l'applicazione degli override del database
// Questo richiederebbe di modificare temporaneamente la stringa di connessione
// delle credenziali per puntare al database specificato
_logger.LogInformation("Applicazione override database - Source: {SourceDB}, Destination: {DestDB}",
sourceDatabaseOverride ?? "none", destinationDatabaseOverride ?? "none");
return await Task.FromResult(profileCopy);
}
private (bool IsValid, string? ErrorMessage) ValidateProfile(DataCouplerProfile profile)
{
if (profile == null)
return (false, "Profilo non specificato");
if (string.IsNullOrEmpty(profile.Name))
return (false, "Nome profilo non specificato");
if (string.IsNullOrEmpty(profile.SourceType))
return (false, "Tipo sorgente non specificato");
if (string.IsNullOrEmpty(profile.DestinationType))
return (false, "Tipo destinazione non specificato");
// Per le sorgenti file, la credenziale non è richiesta
if (profile.SourceType != "file" && !profile.SourceCredentialId.HasValue)
return (false, "Credenziale sorgente non specificata");
if (!profile.DestinationCredentialId.HasValue)
return (false, "Credenziale destinazione non specificata");
// Validazioni specifiche per tipo sorgente
if (profile.SourceType == "database")
{
if (string.IsNullOrEmpty(profile.SourceTable) && string.IsNullOrEmpty(profile.SourceCustomQuery))
return (false, "Tabella sorgente o query personalizzata deve essere specificata");
}
else if (profile.SourceType == "file")
{
if (string.IsNullOrEmpty(profile.SourceFilePath))
return (false, "Percorso file sorgente non specificato");
}
// Validazioni specifiche per tipo destinazione
if (profile.DestinationType == "database")
{
if (string.IsNullOrEmpty(profile.DestinationTable))
return (false, "Tabella destinazione non specificata");
}
else if (profile.DestinationType == "rest")
{
if (string.IsNullOrEmpty(profile.DestinationEndpoint))
return (false, "Endpoint REST destinazione non specificato");
}
return (true, null);
}
}