Files
Data-Coupler/MAPPED_FIELD_FINAL_CORRECTION.md
T
Alessio 5d9b9756cf 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
2025-10-20 00:42:07 +02:00

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

  1. Fermare l'applicazione in esecuzione (attualmente blocca i file DLL)
  2. Ricompilare il progetto:
    dotnet build Data_Coupler.sln
    
  3. Configurare logging Debug in appsettings.Development.json:
    {
      "Logging": {
        "LogLevel": {
          "Data_Coupler.Pages.DataCoupler": "Debug",
          "Data_Coupler.Services.ScheduledProfileExecutionService": "Debug"
        }
      }
    }
    

Scenario di Test

  1. Configurare mapping:

    • Sorgente: CSV con colonne Email, Nome, Cognome
    • Destinazione: Salesforce Contact con campi EmailAddress, FirstName, LastName
    • Mapping:
      • Email → EmailAddress
      • Nome → FirstName
      • Cognome → LastName
  2. Selezionare campo chiave: Email

  3. Eseguire trasferimento dati

  4. 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
    
  5. 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:

  1. Tracciabilità: Sapere quale campo REST API corrisponde alla chiave sorgente
  2. Debugging: Verificare il mapping applicato durante il trasferimento
  3. Audit: Documentare la configurazione utilizzata per ogni associazione
  4. 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 TryGetValue per 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

  1. FERMARE l'applicazione in esecuzione (il processo blocca le DLL)
  2. 🔨 Ricompilare: dotnet build Data_Coupler.sln
  3. ▶️ Riavviare l'applicazione
  4. 🧪 Eseguire test di trasferimento con campo chiave mappato
  5. 📋 Verificare log per messaggio "Trovato mapping"
  6. 🔍 Query database per verificare MappedDestinationField popolato

Data Correzione: 20 Ottobre 2025
Versione: 2.0 - Correzione Finale MappedDestinationField
Status: Implementazione completa, pronto per test