# 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 ```csharp // 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 ```csharp // fieldMappings è strutturato come: Dictionary 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) ```csharp private async Task CreateAssociationAsync(Dictionary 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) ```csharp // 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 ```csharp // 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: ```powershell dotnet build Data_Coupler.sln ``` 3. **Configurare logging Debug** in `appsettings.Development.json`: ```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**: ```sql 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:** ```sql 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 - [x] Correzione logica in `DataCoupler.razor.cs::CreateAssociationAsync` - [x] Correzione logica in `DataCoupler.razor.cs::StartDataTransferOriginal` - [x] Correzione logica in `ScheduledProfileExecutionService.cs::CreateAssociationAsync` - [x] Uso di `TryGetValue` per ricerca sicura nel dictionary - [x] Logging diagnostico completo - [x] 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