fix: Correzione salvataggio campo MappedDestinationField in KeyAssociations
- Aggiunto campo MappedDestinationField al modello KeyAssociation per tracciare il campo destinazione mappato alla chiave sorgente
- Creata migration AddMappedDestinationFieldToKeyAssociation per aggiungere la colonna al database
- Implementata logica di popolamento in CreateAssociationAsync e StartDataTransferOriginal per salvare il campo destinazione mappato
- Aggiornato SaveAssociationParallelAsync per includere MappedDestinationField nelle query SQL UPDATE e INSERT
- Corretti indici parametri nella query UPDATE (da {7-9} a {8-10}) per includere il nuovo campo
- Aggiunta visualizzazione campo nell'interfaccia KeyAssociations (tabella, dettagli, export CSV)
- Implementato controllo validazione per impedire trasferimenti se il campo chiave non è mappato
- Aggiunto logging diagnostico dettagliato per debug del mapping dei campi
- Aggiornato ScheduledProfileExecutionService per popolare MappedDestinationField nelle esecuzioni schedulate
- Rimosso file BackgroundServices.cs obsoleto
- Documentazione completa creata (4 markdown files)
Fixes: Campo MappedDestinationField rimaneva NULL perché le query SQL raw non includevano il nuovo campo
This commit is contained in:
@@ -1,31 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Data_Coupler.BackgrounServices;
|
||||
|
||||
public class BackgroundServices : BackgroundService
|
||||
{
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
while (!stoppingToken.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Qui puoi inserire il codice che vuoi eseguire in background
|
||||
// Ad esempio, puoi chiamare un metodo per eseguire operazioni periodiche
|
||||
|
||||
// Simula un'attività di lunga durata
|
||||
await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// Gestisci l'eccezione se il task viene cancellato
|
||||
break;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Gestisci altre eccezioni
|
||||
Console.WriteLine($"Errore: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1056,6 +1056,16 @@
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@* Messaggio di errore per trasferimento disabilitato *@
|
||||
@if (!IsTransferButtonEnabled() && !string.IsNullOrEmpty(GetTransferDisabledReason()))
|
||||
{
|
||||
<div class="alert alert-warning mt-3" role="alert">
|
||||
<i class="fas fa-exclamation-triangle"></i>
|
||||
<strong>Trasferimento disabilitato:</strong> @GetTransferDisabledReason()
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (!string.IsNullOrEmpty(transferMessage))
|
||||
{
|
||||
<div class="alert @(transferMessageType == "success" ? "alert-success" : transferMessageType == "warning" ? "alert-warning" : "alert-danger") mt-3" role="alert">
|
||||
|
||||
@@ -1396,12 +1396,32 @@ public partial class DataCoupler : ComponentBase
|
||||
{
|
||||
// Determina i campi chiave automaticamente
|
||||
var destinationKeyField = GetEntityIdField(); // Campo chiave nella destinazione
|
||||
|
||||
// Trova il campo destinazione (REST API) mappato al campo chiave sorgente
|
||||
string? mappedDestinationField = null;
|
||||
|
||||
Logger.LogDebug("MAPPING DEBUG: Cercando il campo destinazione mappato al campo chiave sorgente '{SourceKeyField}'", sourceKeyField);
|
||||
Logger.LogDebug("MAPPING DEBUG: Mappings disponibili: {Mappings}", string.Join(", ", fieldMappings.Select(m => $"{m.Key} -> {m.Value}")));
|
||||
|
||||
// Cerca nel dizionario il campo destinazione corrispondente al campo chiave sorgente
|
||||
if (fieldMappings.TryGetValue(sourceKeyField, out var destinationFieldName))
|
||||
{
|
||||
mappedDestinationField = destinationFieldName;
|
||||
Logger.LogDebug("MAPPING DEBUG: Trovato mapping: campo sorgente '{SourceField}' è mappato al campo destinazione '{DestField}'",
|
||||
sourceKeyField, mappedDestinationField);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogWarning("MAPPING DEBUG: Campo chiave sorgente '{SourceKeyField}' NON trovato nei mappings! Il campo MappedDestinationField non verrà popolato.",
|
||||
sourceKeyField);
|
||||
}
|
||||
|
||||
var association = new KeyAssociation
|
||||
{
|
||||
KeyValue = sourceKey,
|
||||
SourceKeyField = sourceKeyField,
|
||||
DestinationKeyField = destinationKeyField,
|
||||
MappedDestinationField = mappedDestinationField, // Campo destinazione mappato al campo chiave sorgente
|
||||
DestinationEntity = selectedRestEntity?.Name ?? "",
|
||||
DestinationId = transferResult.EntityId,
|
||||
RestCredentialName = selectedRestCredential,
|
||||
@@ -1416,8 +1436,8 @@ public partial class DataCoupler : ComponentBase
|
||||
})
|
||||
};
|
||||
|
||||
Logger.LogInformation("ASSOCIATION DEBUG: Creazione nuova associazione - KeyValue: '{KeyValue}', Entity: '{Entity}', DestinationId: '{DestinationId}', Credential: '{Credential}'",
|
||||
sourceKey, selectedRestEntity?.Name ?? "Unknown", transferResult.EntityId, selectedRestCredential);
|
||||
Logger.LogInformation("ASSOCIATION DEBUG: Creazione nuova associazione - KeyValue: '{KeyValue}', Entity: '{Entity}', DestinationId: '{DestinationId}', Credential: '{Credential}', MappedField: '{MappedField}'",
|
||||
sourceKey, selectedRestEntity?.Name ?? "Unknown", transferResult.EntityId, selectedRestCredential, mappedDestinationField ?? "N/A");
|
||||
|
||||
var associationId = await CredentialService.SaveKeyAssociationAsync(association);
|
||||
Logger.LogInformation("DEBUG: Associazione salvata con ID: {AssociationId}", associationId);
|
||||
@@ -1721,9 +1741,36 @@ public partial class DataCoupler : ComponentBase
|
||||
if (useRecordAssociations && string.IsNullOrEmpty(sourceKeyField))
|
||||
return false;
|
||||
|
||||
// Verifica che il campo chiave sia presente nei campi mappati
|
||||
if (useRecordAssociations && !string.IsNullOrEmpty(sourceKeyField))
|
||||
{
|
||||
if (!fieldMappings.ContainsKey(sourceKeyField))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ottiene il messaggio di errore che spiega perché il trasferimento non può essere avviato
|
||||
/// </summary>
|
||||
private string GetTransferDisabledReason()
|
||||
{
|
||||
if (!fieldMappings.Any())
|
||||
return "Nessun campo mappato. Crea almeno un mapping tra i campi sorgente e destinazione.";
|
||||
|
||||
if (useRecordAssociations && string.IsNullOrEmpty(sourceKeyField))
|
||||
return "Campo chiave sorgente non selezionato. Seleziona un campo che identifichi univocamente i record.";
|
||||
|
||||
if (useRecordAssociations && !string.IsNullOrEmpty(sourceKeyField))
|
||||
{
|
||||
if (!fieldMappings.ContainsKey(sourceKeyField))
|
||||
return $"Il campo chiave '{sourceKeyField}' deve essere mappato. Crea un mapping per questo campo prima di procedere con il trasferimento.";
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
// Helper methods per UI risultati
|
||||
private string GetResultRowClass(string status)
|
||||
{
|
||||
@@ -2844,11 +2891,32 @@ public partial class DataCoupler : ComponentBase
|
||||
var finalDataHash = dataHash ?? GenerateDataHash(originalRecord);
|
||||
|
||||
var destinationKeyField = GetEntityIdField();
|
||||
|
||||
// Trova il campo destinazione (REST API) mappato al campo chiave sorgente
|
||||
string? mappedDestinationField = null;
|
||||
|
||||
Logger.LogDebug("MAPPING DEBUG: Cercando il campo destinazione mappato al campo chiave sorgente '{SourceKeyField}'", currentSourceKeyField);
|
||||
Logger.LogDebug("MAPPING DEBUG: Mappings disponibili: {Mappings}", string.Join(", ", fieldMappings.Select(m => $"{m.Key} -> {m.Value}")));
|
||||
|
||||
// Cerca nel dizionario il campo destinazione corrispondente al campo chiave sorgente
|
||||
if (fieldMappings.TryGetValue(currentSourceKeyField, out var destinationFieldName))
|
||||
{
|
||||
mappedDestinationField = destinationFieldName;
|
||||
Logger.LogDebug("MAPPING DEBUG: Trovato mapping: campo sorgente '{SourceField}' è mappato al campo destinazione '{DestField}'",
|
||||
currentSourceKeyField, mappedDestinationField);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogWarning("MAPPING DEBUG: Campo chiave sorgente '{SourceKeyField}' NON trovato nei mappings! Il campo MappedDestinationField non verrà popolato.",
|
||||
currentSourceKeyField);
|
||||
}
|
||||
|
||||
var association = new KeyAssociation
|
||||
{
|
||||
KeyValue = sourceKey,
|
||||
SourceKeyField = currentSourceKeyField,
|
||||
DestinationKeyField = destinationKeyField,
|
||||
MappedDestinationField = mappedDestinationField, // Campo destinazione mappato al campo chiave sorgente
|
||||
DestinationEntity = currentEntityName,
|
||||
DestinationId = entityId,
|
||||
RestCredentialName = currentCredentialName,
|
||||
@@ -2867,8 +2935,8 @@ public partial class DataCoupler : ComponentBase
|
||||
};
|
||||
|
||||
var associationId = await CredentialService.SaveKeyAssociationParallelAsync(association);
|
||||
Logger.LogDebug("COMPOSITE: Associazione creata con ID: {AssociationId} per record {RecordNumber} (PARALLEL) - Hash: {Hash}",
|
||||
associationId, recordNumber, finalDataHash);
|
||||
Logger.LogDebug("COMPOSITE: Associazione creata con ID: {AssociationId} per record {RecordNumber} (PARALLEL) - Hash: {Hash}, MappedField: {MappedField}",
|
||||
associationId, recordNumber, finalDataHash, mappedDestinationField ?? "N/A");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -239,6 +239,7 @@
|
||||
<th>Valore Chiave</th>
|
||||
<th>Campo Sorgente</th>
|
||||
<th>Campo Destinazione</th>
|
||||
<th>Campo Mappato</th>
|
||||
<th>Entità Destinazione</th>
|
||||
<th>ID Destinazione</th>
|
||||
<th>Credenziale</th>
|
||||
@@ -262,6 +263,18 @@
|
||||
<td>
|
||||
<span class="badge bg-secondary">@association.DestinationKeyField</span>
|
||||
</td>
|
||||
<td>
|
||||
@if (!string.IsNullOrEmpty(association.MappedDestinationField))
|
||||
{
|
||||
<span class="badge bg-primary">@association.MappedDestinationField</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="text-muted">
|
||||
<i class="fas fa-minus"></i> N/A
|
||||
</span>
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
<strong>@association.DestinationEntity</strong>
|
||||
</td>
|
||||
@@ -540,9 +553,13 @@
|
||||
info += $"Valore Chiave: {association.KeyValue}\n";
|
||||
info += $"Campo Sorgente: {association.SourceKeyField}\n";
|
||||
info += $"Campo Destinazione: {association.DestinationKeyField}\n";
|
||||
if (!string.IsNullOrEmpty(association.MappedDestinationField))
|
||||
info += $"Campo Mappato: {association.MappedDestinationField}\n";
|
||||
info += $"Entità: {association.DestinationEntity}\n";
|
||||
info += $"ID Destinazione: {association.DestinationId}\n";
|
||||
info += $"Credenziale: {association.RestCredentialName}\n";
|
||||
if (!string.IsNullOrEmpty(association.Data_Hash))
|
||||
info += $"Hash Dati: {association.Data_Hash}\n";
|
||||
info += $"Creata: {association.CreatedAt:dd/MM/yyyy HH:mm}\n";
|
||||
if (association.UpdatedAt.HasValue)
|
||||
info += $"Aggiornata: {association.UpdatedAt:dd/MM/yyyy HH:mm}\n";
|
||||
@@ -650,12 +667,14 @@
|
||||
{
|
||||
try
|
||||
{
|
||||
var csv = "Valore Chiave,Campo Sorgente,Campo Destinazione,Entità Destinazione,ID Destinazione,Credenziale,Stato,Creata,Aggiornata,Verificata\n";
|
||||
var csv = "Valore Chiave,Campo Sorgente,Campo Destinazione,Campo Mappato,Entità Destinazione,ID Destinazione,Credenziale,Hash Dati,Stato,Creata,Aggiornata,Verificata\n";
|
||||
|
||||
foreach (var association in filteredAssociations)
|
||||
{
|
||||
csv += $"\"{association.KeyValue}\",\"{association.SourceKeyField}\",\"{association.DestinationKeyField}\",";
|
||||
csv += $"\"{association.MappedDestinationField ?? ""}\",";
|
||||
csv += $"\"{association.DestinationEntity}\",\"{association.DestinationId}\",\"{association.RestCredentialName}\",";
|
||||
csv += $"\"{association.Data_Hash ?? ""}\",";
|
||||
csv += $"\"{(association.IsActive ? "Attiva" : "Disattivata")}\",\"{association.CreatedAt:dd/MM/yyyy HH:mm}\",";
|
||||
csv += $"\"{(association.UpdatedAt?.ToString("dd/MM/yyyy HH:mm") ?? "")}\",";
|
||||
csv += $"\"{(association.LastVerifiedAt?.ToString("dd/MM/yyyy HH:mm") ?? "")}\"\n";
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
using System;
|
||||
|
||||
namespace Data_Coupler.Services;
|
||||
|
||||
public class AssociationService
|
||||
{
|
||||
|
||||
}
|
||||
@@ -970,14 +970,31 @@ public class ScheduledProfileExecutionService : IScheduledProfileExecutionServic
|
||||
if (string.IsNullOrEmpty(sourceKey))
|
||||
return;
|
||||
|
||||
// Calcola il MappingCount in modo sicuro
|
||||
// Calcola il MappingCount in modo sicuro e trova il campo destinazione mappato al campo chiave sorgente
|
||||
int mappingCount = 0;
|
||||
string? mappedDestinationField = null;
|
||||
try
|
||||
{
|
||||
if (!string.IsNullOrEmpty(profile.FieldMappingJson))
|
||||
{
|
||||
var mappings = ParseFieldMappings(profile.FieldMappingJson);
|
||||
mappingCount = mappings?.Count ?? 0;
|
||||
|
||||
// Cerca il campo destinazione mappato al campo chiave sorgente
|
||||
if (mappings != null && !string.IsNullOrEmpty(profile.SourceKeyField))
|
||||
{
|
||||
if (mappings.TryGetValue(profile.SourceKeyField, out var destinationFieldName))
|
||||
{
|
||||
mappedDestinationField = destinationFieldName;
|
||||
_logger.LogDebug("SCHEDULED MAPPING: Campo sorgente '{SourceField}' è mappato al campo destinazione '{DestField}'",
|
||||
profile.SourceKeyField, mappedDestinationField);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning("SCHEDULED MAPPING: Campo chiave sorgente '{SourceKeyField}' NON trovato nei mappings del profilo {ProfileName}",
|
||||
profile.SourceKeyField, profile.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -990,6 +1007,7 @@ public class ScheduledProfileExecutionService : IScheduledProfileExecutionServic
|
||||
KeyValue = sourceKey,
|
||||
SourceKeyField = profile.SourceKeyField ?? "",
|
||||
DestinationKeyField = "Id", // Campo ID standard per REST
|
||||
MappedDestinationField = mappedDestinationField, // Campo destinazione mappato al campo chiave sorgente
|
||||
DestinationEntity = profile.DestinationEndpoint ?? "",
|
||||
DestinationId = entityId,
|
||||
RestCredentialName = restCredential.Name, // Usa il nome della credenziale
|
||||
@@ -1014,8 +1032,8 @@ public class ScheduledProfileExecutionService : IScheduledProfileExecutionServic
|
||||
|
||||
var associationId = await _dataConnectionCredentialService.SaveKeyAssociationParallelAsync(association);
|
||||
|
||||
_logger.LogDebug("COMPOSITE SCHEDULED: Associazione creata con ID: {AssociationId} per record {RecordNumber} - Key: {SourceKey}, EntityId: {EntityId}, Hash: {Hash}",
|
||||
associationId, recordNumber, sourceKey, entityId, dataHash);
|
||||
_logger.LogDebug("COMPOSITE SCHEDULED: Associazione creata con ID: {AssociationId} per record {RecordNumber} - Key: {SourceKey}, EntityId: {EntityId}, Hash: {Hash}, MappedField: {MappedField}",
|
||||
associationId, recordNumber, sourceKey, entityId, dataHash, mappedDestinationField ?? "N/A");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user