Files
Data-Coupler/MAPPED_FIELD_LOGIC_FIX.md
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

8.8 KiB

Correzione Logica MappedDestinationField

📋 Problema Identificato

Il campo MappedDestinationField nella tabella KeyAssociations non veniva popolato correttamente perché la logica di ricerca era invertita.

Logica Errata Precedente

Il codice cercava di trovare il campo destinazione mappato al campo chiave sorgente:

// SBAGLIATO: Cercava il valore nel dictionary usando sourceKeyField come chiave
if (fieldMappings.ContainsKey(currentSourceKeyField))
{
    mappedDestinationField = fieldMappings[currentSourceKeyField];
}

Problema: Questo non aveva senso perché:

  • Il campo chiave sorgente (SourceKeyField) è già memorizzato separatamente
  • Non serviva sapere a cosa era mappato il campo chiave sorgente
  • Il mapping cambia per ogni trasferimento

Logica Corretta Implementata

Obiettivo del Campo

MappedDestinationField deve memorizzare il campo sorgente che è mappato al campo fisso "DestinationId" nella destinazione REST.

Struttura del Mapping

Nel dictionary fieldMappings:

  • Chiave: Nome del campo nella sorgente (database, CSV, Excel)
  • Valore: Nome del campo nella destinazione (entità REST API)

Esempio:

fieldMappings = new Dictionary<string, object>
{
    { "CodiceFiscale", "DestinationId" },    // Campo da memorizzare
    { "Nome", "FirstName" },
    { "Cognome", "LastName" }
}

In questo caso, MappedDestinationField deve contenere "CodiceFiscale".

Nuova Implementazione

// CORRETTO: Cerca quale campo sorgente è mappato a "DestinationId"
var mappingToDestinationId = fieldMappings.FirstOrDefault(m => m.Value == "DestinationId");
if (!string.IsNullOrEmpty(mappingToDestinationId.Key))
{
    mappedSourceField = mappingToDestinationId.Key;
    Logger.LogDebug("Campo sorgente '{SourceField}' è mappato a 'DestinationId'", mappedSourceField);
}

🔧 File Modificati

1. DataCoupler.razor.cs

Metodo CreateAssociationAsync (linea ~2890)

Prima:

// Trova il campo di destinazione mappato alla chiave sorgente
string? mappedDestinationField = null;
if (fieldMappings.ContainsKey(currentSourceKeyField))
{
    mappedDestinationField = fieldMappings[currentSourceKeyField];
}

Dopo:

// Trova il campo sorgente che è mappato a "DestinationId"
string? mappedSourceField = null;
var mappingToDestinationId = fieldMappings.FirstOrDefault(m => m.Value == "DestinationId");
if (!string.IsNullOrEmpty(mappingToDestinationId.Key))
{
    mappedSourceField = mappingToDestinationId.Key;
}

Metodo StartDataTransferOriginal (linea ~1400)

Stessa correzione applicata anche al metodo di trasferimento originale (non composite).

2. ScheduledProfileExecutionService.cs

Metodo CreateAssociationAsync (linea ~975)

Prima:

int mappingCount = 0;
// ... calcolo mappingCount ...

var association = new KeyAssociation
{
    // ... altri campi ...
    // MappedDestinationField non veniva popolato
};

Dopo:

int mappingCount = 0;
string? mappedSourceField = null;

if (!string.IsNullOrEmpty(profile.FieldMappingJson))
{
    var mappings = ParseFieldMappings(profile.FieldMappingJson);
    
    // Cerca il campo sorgente mappato a "DestinationId"
    var mappingToDestinationId = mappings.FirstOrDefault(m => m.Value == "DestinationId");
    if (!string.IsNullOrEmpty(mappingToDestinationId.Key))
    {
        mappedSourceField = mappingToDestinationId.Key;
    }
}

var association = new KeyAssociation
{
    // ... altri campi ...
    MappedDestinationField = mappedSourceField, // Campo sorgente mappato a DestinationId
};

📊 Logging Diagnostico

Log Implementati

// Traccia ricerca del campo
Logger.LogDebug("MAPPING DEBUG: Cercando quale campo sorgente è mappato a 'DestinationId'");

// Mostra tutti i mapping disponibili
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 a 'DestinationId'", 
    mappedSourceField);

// Fallimento (warning)
Logger.LogWarning("MAPPING DEBUG: Nessun campo sorgente mappato a 'DestinationId'! Il campo non verrà popolato.");

// Log creazione associazione
Logger.LogDebug("COMPOSITE: Associazione creata con ID: {AssociationId} per record {RecordNumber} - Hash: {Hash}, MappedField: {MappedField}", 
    associationId, recordNumber, finalDataHash, mappedSourceField ?? "N/A");

🧪 Testing

Pre-Requisiti per il Test

  1. Fermare l'applicazione in esecuzione
  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 con campo mappato a DestinationId:

    • Esempio: CodiceFiscale (sorgente) → DestinationId (destinazione)
  2. Selezionare campo chiave (può essere diverso dal campo mappato a DestinationId):

    • Esempio: Email come SourceKeyField
  3. Eseguire trasferimento dati

  4. Verificare nei log:

    MAPPING DEBUG: Cercando quale campo sorgente è mappato a 'DestinationId'
    MAPPING DEBUG: Mappings disponibili: CodiceFiscale -> DestinationId, Nome -> FirstName, ...
    MAPPING DEBUG: Trovato mapping: campo sorgente 'CodiceFiscale' è mappato a 'DestinationId'
    COMPOSITE: Associazione creata con ID: 123 - MappedField: CodiceFiscale
    
  5. Verificare nel database:

    SELECT 
        SourceKeyField,           -- Campo chiave sorgente (es. "Email")
        MappedDestinationField,   -- Campo sorgente mappato a DestinationId (es. "CodiceFiscale")
        DestinationKeyField,      -- Sempre "Id" o "DestinationId"
        KeyValue,                 -- Valore del campo chiave
        DestinationId             -- ID generato dalla REST API
    FROM KeyAssociations
    ORDER BY CreatedAt DESC
    LIMIT 5;
    

Risultato Atteso

SourceKeyField         | MappedDestinationField | DestinationKeyField | KeyValue              | DestinationId
-----------------------|------------------------|---------------------|----------------------|---------------
Email                  | CodiceFiscale          | Id                  | user@example.com     | ABC123XYZ
Email                  | CodiceFiscale          | Id                  | admin@example.com    | DEF456UVW

📝 Spiegazione Concettuale

Differenza tra i Campi

Campo Descrizione Esempio
SourceKeyField Campo usato come chiave univoca nella sorgente "Email"
KeyValue Valore specifico del campo chiave per questo record "user@example.com"
MappedDestinationField Campo sorgente mappato a DestinationId nella REST API "CodiceFiscale"
DestinationKeyField Campo chiave nella destinazione (sempre "Id" o "DestinationId") "Id"
DestinationId ID univoco generato dalla REST API "ABC123XYZ"

Perché è Importante

Durante il coupling, il sistema deve:

  1. Identificare record esistenti: Usa SourceKeyField e KeyValue
  2. Popolare DestinationId: Usa il valore del campo sorgente specificato in MappedDestinationField
  3. Evitare duplicati: Verifica se esiste già un'associazione con lo stesso KeyValue

Esempio pratico:

Record CSV:
- Email: "user@example.com"         <- Usato come SourceKeyField per identificare il record
- CodiceFiscale: "RSSMRA80A01H501U" <- Valore da mettere in DestinationId

Mapping:
- CodiceFiscale → DestinationId     <- MappedDestinationField = "CodiceFiscale"
- Email → EmailAddress
- Nome → FirstName

Associazione creata:
- SourceKeyField: "Email"
- KeyValue: "user@example.com"
- MappedDestinationField: "CodiceFiscale"
- DestinationId: "RSSMRA80A01H501U"  <- Preso dal campo CodiceFiscale del record

Checklist Verifica

  • Correzione logica in DataCoupler.razor.cs::CreateAssociationAsync
  • Correzione logica in DataCoupler.razor.cs::StartDataTransferOriginal
  • Correzione logica in ScheduledProfileExecutionService.cs::CreateAssociationAsync
  • Aggiunta logging diagnostico in tutti i metodi
  • Verifica assenza errori di compilazione
  • Test con trasferimento reale
  • Verifica popolamento campo nel database
  • Verifica log output corretto

🎯 Prossimi Passi

  1. Fermare applicazione in esecuzione
  2. Ricompilare progetto
  3. Riavviare applicazione
  4. Eseguire test trasferimento
  5. Verificare log output (cercare "MAPPING DEBUG")
  6. Query database per confermare campo popolato

Data Correzione: 20 Ottobre 2025
Versione: 1.0 - Correzione Logica MappedDestinationField