- 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
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 → JSONDeserializeExternalIdRelationships()- 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:
- Selezione Oggetto Correlato: Dropdown con tutti gli oggetti REST disponibili
- Selezione External ID Field: Dropdown con campi filtrati (terminanti con
__c,Id, contengono "External") - Selezione Campo Sorgente: Dropdown con campi disponibili dalla sorgente dati
- Pulsante Aggiungi: Conferma e aggiunge relazione alla lista
- 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 oggettoAddExternalIdRelationship()- Aggiunge nuova relazione con validazioneRemoveExternalIdRelationship()- Rimuove relazione esistenteGetExternalIdFieldsForSelectedObject()- Ottiene campi External ID disponibiliGetSourceFieldsForRelationship()- 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 popolareavailableRelationshipObjects - 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:
-
20260203000000_AddExternalIdRelationships.cs
- Migrazione Entity Framework per aggiungere campo
ExternalIdRelationshipsJson - Tipo: TEXT, MaxLength: 4000, Nullable
- Migrazione Entity Framework per aggiungere campo
-
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
- Nessun ID Salesforce Richiesto: Non serve conoscere l'ID Salesforce dell'Account
- Lookup Automatico: Salesforce cerca automaticamente l'Account con
Country__c = "US" - Upsert Intelligente: Se non trova l'Account, può crearlo automaticamente (se configurato)
- Manutenzione Semplificata: I codici esterni sono più stabili degli ID interni
🔄 Flusso Operativo
Configurazione Manuale (DataCoupler.razor)
- Utente configura connessione sorgente (database/file) e destinazione (Salesforce)
- Sistema scopre automaticamente oggetti REST disponibili
- Utente configura field mappings principali
- Sezione External ID Relationships diventa visibile
- Utente seleziona:
- Oggetto correlato (es: Account)
- Campo External ID (es: Country__c)
- Campo sorgente (es: CountryCode)
- Click su "Aggiungi Relazione" → validazione e aggiunta alla lista
- (Opzionale) Salvataggio come profilo per riutilizzo futuro
- Esecuzione trasferimento → relazioni applicate automaticamente
Esecuzione Schedulata (ScheduledProfileExecutionService)
- Background service carica profilo dal database
- Deserializza External ID Relationships da JSON
- Estrae dati dalla sorgente
- Trasforma ogni record applicando field mappings + External ID Relationships
- Invia a Salesforce (Standard API o Composite API)
- Gestisce associazioni record e hash per evitare duplicati
🧪 Testing
Scenari di Test Consigliati
-
Configurazione UI
- ✅ Selezione oggetti e campi funziona correttamente
- ✅ Validazione impedisce relazioni incomplete
- ✅ Aggiunta e rimozione relazioni aggiorna UI
-
Salvataggio/Caricamento Profili
- ✅ Relazioni salvate correttamente in JSON
- ✅ Profilo ricaricato ripristina tutte le relazioni
- ✅ Database persiste ExternalIdRelationshipsJson
-
Trasformazione Dati
- ✅ Record trasformato include dizionario annidato per relazioni
- ✅ Valori null/vuoti gestiti correttamente
- ✅ Logging dettagliato per ogni relazione aggiunta
-
Esecuzione Schedulata
- ✅ Schedulazione carica e applica relazioni
- ✅ Funziona sia con Standard API che Composite API
- ✅ Errori gestiti e loggati senza bloccare il flusso
-
Integrazione Salesforce
- ✅ Salesforce accetta formato External ID Relationship
- ✅ Lookup automatico funziona correttamente
- ✅ Record creati con relazioni corrette
📝 Note Implementative
Decisioni di Design
-
MaxLength JSON: 4000 caratteri
- Ragionamento: Supporta configurazioni complesse senza eccedere limiti SQLite
- Alternativa: Se necessario più spazio, può essere aumentato a TEXT illimitato
-
Parametro Opzionale in TransformRecordForRest
- Backward compatibility garantita
- Chiamate esistenti senza External ID continuano a funzionare
-
Filtro Campi External ID
- Logica:
EndsWith("__c") || Name == "Id" || Contains("External") - Copre la maggior parte dei casi comuni in Salesforce
- Personalizzabile se necessario
- Logica:
-
Visibilità Condizionale UI
- Solo per Salesforce (verifica
IsSalesforceClient()) - Solo dopo field mappings configurati (
fieldMappings.Any()) - Migliora UX evitando confusione per altre API
- Solo per Salesforce (verifica
Potenziali Estensioni Future
- Validazione Avanzata: Verifica esistenza oggetto/campo su Salesforce prima di salvare
- Multi-Level Relationships: Supporto per relazioni annidate (es:
Account__r.Owner__r.Name__c) - Relazioni Composite: Più External ID per stesso oggetto (es: FirstName + LastName)
- Import/Export Relazioni: Backup e restore separato delle configurazioni relazioni
- 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
- Salesforce External ID Documentation
- Salesforce REST API - Insert or Update
- Salesforce Relationship Fields
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