- 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
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
- Fermare l'applicazione in esecuzione
- 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 con campo mappato a
DestinationId:- Esempio:
CodiceFiscale(sorgente) →DestinationId(destinazione)
- Esempio:
-
Selezionare campo chiave (può essere diverso dal campo mappato a DestinationId):
- Esempio:
Emailcome SourceKeyField
- Esempio:
-
Eseguire trasferimento dati
-
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 -
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:
- Identificare record esistenti: Usa
SourceKeyFieldeKeyValue - Popolare DestinationId: Usa il valore del campo sorgente specificato in
MappedDestinationField - 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
- Fermare applicazione in esecuzione
- Ricompilare progetto
- Riavviare applicazione
- Eseguire test trasferimento
- Verificare log output (cercare "MAPPING DEBUG")
- Query database per confermare campo popolato
Data Correzione: 20 Ottobre 2025
Versione: 1.0 - Correzione Logica MappedDestinationField