Files
Data-Coupler/EXTERNAL_ID_RELATIONSHIPS_IMPLEMENTATION.md
T
Alessio 483eb7b407 Fix: Risolto double-mapping negli External ID Relationships per Salesforce
- Implementata funzionalità completa External ID Relationships nell'interfaccia di mapping
- Corretto bug double-mapping: i campi sorgente usati per External ID non vengono più inclusi nei mapping normali
- Risolto errore MALFORMED_ID causato dall'invio duplicato di campi come proprietà dirette e nested objects
- Implementata logica corretta per relationship names: oggetti standard usano il nome diretto, custom objects usano suffisso __r
- Aggiunta UI a 3 colonne (Object, External ID Field, Source Field) per configurazione External ID Relationships
- Migrazione database per supporto External ID Relationships nei profili
- Aggiornato ProfileSaver.razor.cs per salvare/caricare External ID Relationships
- Aggiornato ScheduledProfileExecutionService.cs per gestire External ID nelle esecuzioni schedulate
- Formato JSON output corretto: { 'Account': { 'CardCode__c': 'V50000' } }

Documentazione: EXTERNAL_ID_RELATIONSHIPS_IMPLEMENTATION.md
2026-02-15 18:44:15 +01:00

13 KiB

Implementazione External ID Relationships per Salesforce

📋 Panoramica

Implementata la funzionalità completa per gestire External ID Relationships nell'interfaccia di mapping dei campi di Data-Coupler. Questa feature permette di creare relazioni tra oggetti Salesforce utilizzando External ID durante il trasferimento dati, evitando la necessità di conoscere gli ID Salesforce interni.

🎯 Obiettivi Raggiunti

  • Estensione modelli dati (DTO ed Entity) per supportare External ID Relationships
  • UI completa per configurazione relazioni con autocomplete
  • Logica di trasformazione dati integrata in DataCoupler e ScheduledProfileExecutionService
  • Supporto per salvataggio e caricamento relazioni in profili Data Coupler
  • Migrazione database per persistenza configurazioni
  • Supporto per esecuzioni schedulate

🏗️ Architettura Implementata

1. Modelli Dati

ExternalIdRelationshipDto (CredentialManager/Models/DataCouplerProfileDto.cs)

public class ExternalIdRelationshipDto
{
    public string RelationshipName { get; set; } = string.Empty;      // Es: "Account__r"
    public string RelatedObjectName { get; set; } = string.Empty;     // Es: "Account"
    public string ExternalIdField { get; set; } = string.Empty;       // Es: "Country__c"
    public string SourceField { get; set; } = string.Empty;           // Campo sorgente con valore
}

DataCouplerProfile Entity (CredentialManager/Models/DataCouplerProfile.cs)

[MaxLength(4000)]
public string? ExternalIdRelationshipsJson { get; set; }

2. Serializzazione/Deserializzazione

DataCouplerProfileService (CredentialManager/Services/DataCouplerProfileService.cs)

Metodi Aggiunti:

  • SerializeExternalIdRelationships() - Serializza lista DTO → JSON
  • DeserializeExternalIdRelationships() - Deserializza JSON → lista DTO
  • Aggiornato ToDto() per includere External ID Relationships
  • Aggiornato FromDto() per serializzare relazioni
  • Aggiornato UpdateProfileAsync() per persistere ExternalIdRelationshipsJson

3. Interfaccia Utente

DataCoupler.razor - Sezione External ID Relationships

Componenti UI:

  1. Selezione Oggetto Correlato: Dropdown con tutti gli oggetti REST disponibili
  2. Selezione External ID Field: Dropdown con campi filtrati (terminanti con __c, Id, contengono "External")
  3. Selezione Campo Sorgente: Dropdown con campi disponibili dalla sorgente dati
  4. Pulsante Aggiungi: Conferma e aggiunge relazione alla lista
  5. Tabella Relazioni: Visualizza tutte le relazioni configurate con formato di esempio

Visibilità Condizionale:

@if (fieldMappings.Any() && currentRestDiscovery != null && IsSalesforceClient())
  • Mostrata solo per connessioni Salesforce
  • Solo dopo aver configurato i field mappings principali

DataCoupler.razor.cs - Gestione Relazioni

Campi Aggiunti:

private List<ExternalIdRelationshipDto> externalIdRelationships = new();
private string selectedRelationshipObject = string.Empty;
private string selectedExternalIdField = string.Empty;
private string selectedRelationshipSourceField = string.Empty;
private List<RestEntityInfo> availableRelationshipObjects = new();

Metodi Implementati:

  • OnRelationshipObjectSelected() - Gestisce selezione oggetto
  • AddExternalIdRelationship() - Aggiunge nuova relazione con validazione
  • RemoveExternalIdRelationship() - Rimuove relazione esistente
  • GetExternalIdFieldsForSelectedObject() - Ottiene campi External ID disponibili
  • GetSourceFieldsForRelationship() - Ottiene campi sorgente per mapping

Integrazione Reset/Clear:

  • Aggiornato ClearAllMappings() per pulire relazioni
  • Aggiornato ResetAllState() per reset completo
  • Aggiornato ApplyProfileConfiguration() per caricare relazioni da profilo

4. Trasformazione Dati

DataCoupler.razor.cs - TransformRecordToRestEntity()

// Aggiungi External ID Relationships (per Salesforce)
if (externalIdRelationships.Any())
{
    foreach (var relationship in externalIdRelationships)
    {
        if (!string.IsNullOrWhiteSpace(relationship.SourceField) && 
            dbRecord.ContainsKey(relationship.SourceField))
        {
            var sourceValue = dbRecord[relationship.SourceField];
            var transformedValue = TransformValue(sourceValue, relationship.SourceField, relationship.ExternalIdField);

            if (transformedValue != null)
            {
                // Formato: { "Account__r": { "Country__c": "US" } }
                var externalIdObject = new Dictionary<string, object>
                {
                    { relationship.ExternalIdField, transformedValue }
                };

                restData[relationship.RelationshipName] = externalIdObject;
            }
        }
    }
}

ScheduledProfileExecutionService - TransformRecordForRest()

Modifiche:

  • Aggiunto parametro opzionale List<ExternalIdRelationshipDto>? externalIdRelationships
  • Implementata stessa logica di trasformazione per esecuzioni schedulate
  • Aggiornato ExecuteDataTransferAsync() per deserializzare e passare relazioni
  • Aggiornato ExecuteDataTransferStandardAsync() per accettare e usare relazioni
  • Aggiornato ExecuteDataTransferWithCompositeAsync() per supporto Salesforce Composite API

Nuovo Metodo:

private List<ExternalIdRelationshipDto> ParseExternalIdRelationships(string? externalIdRelationshipsJson)
{
    // Deserializza JSON con stesse opzioni di DataCouplerProfileService
    var options = new JsonSerializerOptions 
    { 
        PropertyNamingPolicy = JsonNamingPolicy.CamelCase 
    };
    
    return JsonSerializer.Deserialize<List<ExternalIdRelationshipDto>>(externalIdRelationshipsJson, options);
}

5. Salvataggio Profili

Components/ProfileSaver.razor.cs

Modifiche:

  • Aggiunto parametro ExternalIdRelationships
  • Incluso nella creazione del DTO per salvataggio profili
[Parameter]
public List<ExternalIdRelationshipDto> ExternalIdRelationships { get; set; } = new();

// In SaveProfile()
ExternalIdRelationships = this.ExternalIdRelationships,

6. Discovery REST API

Data_Coupler/Extensions/DataCoupler/RESTMethod.cs

Modifiche:

  • Aggiornato ConnectToRestApi() per popolare availableRelationshipObjects
  • Chiamata a DiscoverEntitiesAsync() per ottenere dettagli completi oggetti REST
try
{
    availableRelationshipObjects = (await currentRestDiscovery.DiscoverEntitiesAsync()).ToList();
    Logger.LogInformation("Caricati {Count} oggetti REST per External ID Relationships", availableRelationshipObjects.Count);
}
catch (Exception ex)
{
    Logger.LogWarning(ex, "Impossibile caricare oggetti REST per External ID Relationships");
}

7. Migrazione Database

File Creati:

  1. 20260203000000_AddExternalIdRelationships.cs

    • Migrazione Entity Framework per aggiungere campo ExternalIdRelationshipsJson
    • Tipo: TEXT, MaxLength: 4000, Nullable
  2. 20260203000000_AddExternalIdRelationships.sql

    • Script SQL manuale per applicazione diretta se necessario
    • Include update di __EFMigrationsHistory
ALTER TABLE DataCouplerProfiles ADD COLUMN ExternalIdRelationshipsJson TEXT;
INSERT INTO __EFMigrationsHistory (MigrationId, ProductVersion)
VALUES ('20260203000000_AddExternalIdRelationships', '9.0.0');

📊 Formato Dati Salesforce

Esempio di Trasformazione

Configurazione:

  • Relationship Name: Account__r
  • Related Object: Account
  • External ID Field: Country__c
  • Source Field: CountryCode (dalla tabella sorgente)

Record Sorgente:

{
  "ProductName": "Widget A",
  "Price": 99.99,
  "CountryCode": "US"
}

Record Trasformato per Salesforce:

{
  "Name": "Widget A",
  "Price__c": 99.99,
  "Account__r": {
    "Country__c": "US"
  }
}

Vantaggi External ID

  1. Nessun ID Salesforce Richiesto: Non serve conoscere l'ID Salesforce dell'Account
  2. Lookup Automatico: Salesforce cerca automaticamente l'Account con Country__c = "US"
  3. Upsert Intelligente: Se non trova l'Account, può crearlo automaticamente (se configurato)
  4. Manutenzione Semplificata: I codici esterni sono più stabili degli ID interni

🔄 Flusso Operativo

Configurazione Manuale (DataCoupler.razor)

  1. Utente configura connessione sorgente (database/file) e destinazione (Salesforce)
  2. Sistema scopre automaticamente oggetti REST disponibili
  3. Utente configura field mappings principali
  4. Sezione External ID Relationships diventa visibile
  5. Utente seleziona:
    • Oggetto correlato (es: Account)
    • Campo External ID (es: Country__c)
    • Campo sorgente (es: CountryCode)
  6. Click su "Aggiungi Relazione" → validazione e aggiunta alla lista
  7. (Opzionale) Salvataggio come profilo per riutilizzo futuro
  8. Esecuzione trasferimento → relazioni applicate automaticamente

Esecuzione Schedulata (ScheduledProfileExecutionService)

  1. Background service carica profilo dal database
  2. Deserializza External ID Relationships da JSON
  3. Estrae dati dalla sorgente
  4. Trasforma ogni record applicando field mappings + External ID Relationships
  5. Invia a Salesforce (Standard API o Composite API)
  6. Gestisce associazioni record e hash per evitare duplicati

🧪 Testing

Scenari di Test Consigliati

  1. Configurazione UI

    • Selezione oggetti e campi funziona correttamente
    • Validazione impedisce relazioni incomplete
    • Aggiunta e rimozione relazioni aggiorna UI
  2. Salvataggio/Caricamento Profili

    • Relazioni salvate correttamente in JSON
    • Profilo ricaricato ripristina tutte le relazioni
    • Database persiste ExternalIdRelationshipsJson
  3. Trasformazione Dati

    • Record trasformato include dizionario annidato per relazioni
    • Valori null/vuoti gestiti correttamente
    • Logging dettagliato per ogni relazione aggiunta
  4. Esecuzione Schedulata

    • Schedulazione carica e applica relazioni
    • Funziona sia con Standard API che Composite API
    • Errori gestiti e loggati senza bloccare il flusso
  5. Integrazione Salesforce

    • Salesforce accetta formato External ID Relationship
    • Lookup automatico funziona correttamente
    • Record creati con relazioni corrette

📝 Note Implementative

Decisioni di Design

  1. MaxLength JSON: 4000 caratteri

    • Ragionamento: Supporta configurazioni complesse senza eccedere limiti SQLite
    • Alternativa: Se necessario più spazio, può essere aumentato a TEXT illimitato
  2. Parametro Opzionale in TransformRecordForRest

    • Backward compatibility garantita
    • Chiamate esistenti senza External ID continuano a funzionare
  3. Filtro Campi External ID

    • Logica: EndsWith("__c") || Name == "Id" || Contains("External")
    • Copre la maggior parte dei casi comuni in Salesforce
    • Personalizzabile se necessario
  4. Visibilità Condizionale UI

    • Solo per Salesforce (verifica IsSalesforceClient())
    • Solo dopo field mappings configurati (fieldMappings.Any())
    • Migliora UX evitando confusione per altre API

Potenziali Estensioni Future

  1. Validazione Avanzata: Verifica esistenza oggetto/campo su Salesforce prima di salvare
  2. Multi-Level Relationships: Supporto per relazioni annidate (es: Account__r.Owner__r.Name__c)
  3. Relazioni Composite: Più External ID per stesso oggetto (es: FirstName + LastName)
  4. Import/Export Relazioni: Backup e restore separato delle configurazioni relazioni
  5. Template Relazioni: Libreria di relazioni predefinite per oggetti Salesforce comuni

🐛 Troubleshooting

Errori Comuni

Errore: "External ID field not found"

  • Causa: Campo External ID non esiste sull'oggetto Salesforce
  • Soluzione: Verificare che il campo sia configurato come External ID in Salesforce

Errore: "Multiple records found with external ID"

  • Causa: External ID non è univoco in Salesforce
  • Soluzione: Verificare unicità del campo External ID

Relazioni Non Applicate

  • Causa: externalIdRelationships è vuoto
  • Soluzione: Verificare deserializzazione JSON in profilo

UI Non Mostra Sezione Relazioni

  • Causa: Condizione visibilità non soddisfatta
  • Soluzione: Verificare che sia Salesforce e field mappings configurati

📚 Riferimenti


Implementazione Completata: 3 Febbraio 2026
Framework: .NET 9.0
Pattern: Repository + DTO + Service Layer
Database: SQLite con Entity Framework Core
UI: Blazor Server con Bootstrap 5