- 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
12 KiB
Correzione Finale MappedDestinationField
📋 Problema Risolto
Il campo MappedDestinationField nella tabella KeyAssociations deve memorizzare il campo destinazione (REST API) che è mappato al campo chiave sorgente.
✅ Logica Corretta Implementata
Obiettivo del Campo
MappedDestinationField memorizza il campo destinazione nella REST API che corrisponde al campo chiave sorgente selezionato dall'utente.
Esempio Pratico
Scenario:
Sorgente (CSV/Database):
- Email: "user@example.com" <- Campo chiave selezionato (SourceKeyField)
- Nome: "Mario"
- Cognome: "Rossi"
- CodiceFiscale: "RSSMRA80A01H501U"
Mappings configurati dall'utente:
Email → EmailAddress <- MappedDestinationField deve essere "EmailAddress"
Nome → FirstName
Cognome → LastName
CodiceFiscale → TaxCode
Campo chiave selezionato: Email
Risultato nel database:
KeyAssociation:
- SourceKeyField: "Email" <- Campo sorgente usato come chiave
- KeyValue: "user@example.com" <- Valore del campo chiave
- MappedDestinationField: "EmailAddress" <- Campo destinazione mappato
- DestinationKeyField: "Id" <- Campo ID nella REST API
- DestinationId: "ABC123" <- ID generato dalla REST API
🔧 Implementazione Corretta
Logica di Ricerca
// Trova il campo destinazione (REST API) mappato al campo chiave sorgente
string? mappedDestinationField = null;
// Usa TryGetValue per cercare nel dictionary
if (fieldMappings.TryGetValue(sourceKeyField, out var destinationFieldName))
{
// Trovato! destinationFieldName contiene il campo destinazione
mappedDestinationField = destinationFieldName;
Logger.LogDebug("Campo sorgente '{SourceField}' è mappato al campo destinazione '{DestField}'",
sourceKeyField, mappedDestinationField);
}
else
{
// Non trovato nei mappings
Logger.LogWarning("Campo chiave sorgente '{SourceKeyField}' NON trovato nei mappings!",
sourceKeyField);
}
Dictionary Structure
// fieldMappings è strutturato come:
Dictionary<string, string> fieldMappings = new()
{
// Chiave = Campo Sorgente → Valore = Campo Destinazione
{ "Email", "EmailAddress" }, // sourceKeyField="Email" → MappedDestinationField="EmailAddress"
{ "Nome", "FirstName" },
{ "Cognome", "LastName" },
{ "CodiceFiscale", "TaxCode" }
};
📝 File Modificati
1. DataCoupler.razor.cs
Metodo CreateAssociationAsync (linea ~2876)
private async Task CreateAssociationAsync(Dictionary<string, object> originalRecord, string entityId, int recordNumber, string? dataHash = null)
{
// ...
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
// ...
};
// ...
}
Metodo StartDataTransferOriginal (linea ~1400)
Stessa logica applicata al metodo di trasferimento originale (non composite).
2. ScheduledProfileExecutionService.cs
Metodo CreateAssociationAsync (linea ~975)
// Calcola il MappingCount in modo sicuro e trova il campo destinazione mappato al campo chiave sorgente
int mappingCount = 0;
string? mappedDestinationField = null;
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);
}
}
}
var association = new KeyAssociation
{
KeyValue = sourceKey,
SourceKeyField = profile.SourceKeyField ?? "",
DestinationKeyField = "Id",
MappedDestinationField = mappedDestinationField, // Campo destinazione mappato al campo chiave sorgente
// ...
};
📊 Logging Diagnostico
Log di Debug
// Inizio ricerca
Logger.LogDebug("MAPPING DEBUG: Cercando il campo destinazione mappato al campo chiave sorgente '{SourceKeyField}'",
sourceKeyField);
// Mostra tutti i mappings
Logger.LogDebug("MAPPING DEBUG: Mappings disponibili: {Mappings}",
string.Join(", ", fieldMappings.Select(m => $"{m.Key} -> {m.Value}")));
// Successo
Logger.LogDebug("MAPPING DEBUG: Trovato mapping: campo sorgente '{SourceField}' è mappato al campo destinazione '{DestField}'",
sourceKeyField, mappedDestinationField);
// Fallimento (warning)
Logger.LogWarning("MAPPING DEBUG: Campo chiave sorgente '{SourceKeyField}' NON trovato nei mappings! Il campo MappedDestinationField non verrà popolato.",
sourceKeyField);
// Creazione associazione
Logger.LogDebug("COMPOSITE: Associazione creata con ID: {AssociationId} per record {RecordNumber} - Hash: {Hash}, MappedField: {MappedField}",
associationId, recordNumber, finalDataHash, mappedDestinationField ?? "N/A");
🧪 Testing
Pre-Requisiti
- Fermare l'applicazione in esecuzione (attualmente blocca i file DLL)
- Ricompilare il progetto:
dotnet build Data_Coupler.sln - Configurare logging Debug in
appsettings.Development.json:{ "Logging": { "LogLevel": { "Data_Coupler.Pages.DataCoupler": "Debug", "Data_Coupler.Services.ScheduledProfileExecutionService": "Debug" } } }
Scenario di Test
-
Configurare mapping:
- Sorgente: CSV con colonne
Email,Nome,Cognome - Destinazione: Salesforce Contact con campi
EmailAddress,FirstName,LastName - Mapping:
- Email → EmailAddress
- Nome → FirstName
- Cognome → LastName
- Sorgente: CSV con colonne
-
Selezionare campo chiave:
Email -
Eseguire trasferimento dati
-
Verificare nei log:
MAPPING DEBUG: Cercando il campo destinazione mappato al campo chiave sorgente 'Email' MAPPING DEBUG: Mappings disponibili: Email -> EmailAddress, Nome -> FirstName, Cognome -> LastName MAPPING DEBUG: Trovato mapping: campo sorgente 'Email' è mappato al campo destinazione 'EmailAddress' COMPOSITE: Associazione creata con ID: 123 - MappedField: EmailAddress -
Verificare nel database:
SELECT Id, SourceKeyField, -- 'Email' KeyValue, -- 'user@example.com' MappedDestinationField, -- 'EmailAddress' ← DEVE ESSERE POPOLATO! DestinationKeyField, -- 'Id' DestinationId, -- 'ABC123XYZ' DestinationEntity, -- 'Contact' RestCredentialName -- 'Salesforce_Prod' FROM KeyAssociations ORDER BY CreatedAt DESC LIMIT 5;
Risultato Atteso
Id | SourceKeyField | KeyValue | MappedDestinationField | DestinationKeyField | DestinationId
----|----------------|-------------------|------------------------|---------------------|---------------
1 | Email | user@example.com | EmailAddress | Id | ABC123XYZ
2 | Email | admin@example.com | EmailAddress | Id | DEF456UVW
📐 Schema dei Campi
| Campo | Tipo | Descrizione | Esempio |
|---|---|---|---|
SourceKeyField |
Campo sorgente | Campo usato come chiave univoca nella sorgente | "Email" |
KeyValue |
Valore | Valore specifico del campo chiave per questo record | "user@example.com" |
MappedDestinationField |
Campo destinazione | Campo REST API mappato al campo chiave sorgente | "EmailAddress" |
DestinationKeyField |
Campo destinazione | Campo ID nella destinazione REST API (sempre "Id") | "Id" |
DestinationId |
ID generato | ID univoco generato dalla REST API dopo creazione | "ABC123XYZ" |
🔍 Perché è Importante
Il campo MappedDestinationField serve per:
- Tracciabilità: Sapere quale campo REST API corrisponde alla chiave sorgente
- Debugging: Verificare il mapping applicato durante il trasferimento
- Audit: Documentare la configurazione utilizzata per ogni associazione
- Ricostruzione: Poter ricreare il mapping originale se necessario
Caso d'Uso Reale
Scenario: Un utente vuole sapere quale campo Salesforce è stato usato per l'email quando ha fatto il coupling.
Query:
SELECT
SourceKeyField, -- 'Email'
MappedDestinationField -- 'EmailAddress'
FROM KeyAssociations
WHERE DestinationEntity = 'Contact'
AND RestCredentialName = 'Salesforce_Prod'
LIMIT 1;
Risposta: "Il campo sorgente Email è stato mappato al campo Salesforce EmailAddress"
✅ Checklist Verifica
- Correzione logica in
DataCoupler.razor.cs::CreateAssociationAsync - Correzione logica in
DataCoupler.razor.cs::StartDataTransferOriginal - Correzione logica in
ScheduledProfileExecutionService.cs::CreateAssociationAsync - Uso di
TryGetValueper ricerca sicura nel dictionary - Logging diagnostico completo
- Verifica assenza errori di compilazione
- Fermare applicazione in esecuzione
- Ricompilare progetto
- Riavviare applicazione
- Test con trasferimento reale
- Verificare log output (cercare "MAPPING DEBUG")
- Query database per confermare campo popolato
🎯 Prossimi Passi IMMEDIATI
- ⛔ FERMARE l'applicazione in esecuzione (il processo blocca le DLL)
- 🔨 Ricompilare:
dotnet build Data_Coupler.sln - ▶️ Riavviare l'applicazione
- 🧪 Eseguire test di trasferimento con campo chiave mappato
- 📋 Verificare log per messaggio "Trovato mapping"
- 🔍 Query database per verificare
MappedDestinationFieldpopolato
Data Correzione: 20 Ottobre 2025
Versione: 2.0 - Correzione Finale MappedDestinationField
Status: ✅ Implementazione completa, pronto per test