- Fix FindEntitiesByKeysAsync: SOQL query universale al posto di External ID GET * Disabilitato External ID GET (funziona solo per campi marcati External ID) * Query SOQL come metodo primario funzionante per tutti i campi * Logging dettagliato per diagnostica ricerche Salesforce - Pre-Discovery nei trasferimenti manuali (DataCoupler.razor.cs) * Ricerca automatica nella destinazione quando KeyAssociation non esiste * Creazione associazione con marker CreatedBy="PreDiscovery" * Aggiornamento forzato senza controllo hash per associazioni Pre-Discovery * Supporto parallel processing thread-safe - Pre-Discovery nei trasferimenti schedulati (ScheduledProfileExecutionService.cs) * Logica identica a trasferimenti manuali * Marker aggiuntivo ScheduledTransfer=true per tracciabilità * Aggiornamento forzato per prima sincronizzazione - Sistema aggiornamento forzato * Verifica AdditionalInfo per identificare associazioni Pre-Discovery * Skip controllo hash per associazioni appena scoperte * Garantisce sincronizzazione dati al primo trasferimento Vantaggi: - Prevenzione automatica duplicati in Salesforce - Recupero record esistenti senza associazioni - Parità funzionale tra esecuzioni manuali e schedulate - Performance ottimizzate con controllo hash per esecuzioni successive Docs: PRE_DISCOVERY_SYSTEM.md, PRE_DISCOVERY_FORCED_UPDATE.md, SALESFORCE_FIND_ENTITIES_FIX.md
11 KiB
Pre-Discovery: Aggiornamento Forzato
📋 Requisito Implementato
Quando il Pre-Discovery trova un record esistente nella destinazione e crea una nuova associazione, il sistema deve forzare l'aggiornamento del record saltando il controllo hash.
🎯 Rationale
Quando un'associazione viene creata dal Pre-Discovery significa che:
- ✅ Il record esiste nella destinazione Salesforce
- ❌ Non esisteva un'associazione in
KeyAssociations - 🔍 I dati potrebbero essere diversi tra sorgente e destinazione
- 🔄 Vogliamo sincronizzare i dati al primo trasferimento
Quindi ha senso forzare l'aggiornamento senza controllare l'hash, per assicurarci che i dati siano allineati.
🔧 Implementazione
Identificazione Associazioni Pre-Discovery
Le associazioni create dal Pre-Discovery hanno un marker nel campo AdditionalInfo:
{
"CreatedBy": "PreDiscovery",
"DiscoveredAt": "2025-10-21T10:30:00Z",
"MappingCount": 5
}
Logica di Controllo
Il sistema verifica il campo AdditionalInfo per determinare se l'associazione proviene dal Pre-Discovery:
// Verifica se l'associazione è stata creata dal Pre-Discovery
var isPreDiscoveryAssociation = false;
if (!string.IsNullOrEmpty(existingAssociation.AdditionalInfo))
{
try
{
var additionalInfo = JsonSerializer.Deserialize<Dictionary<string, object>>(existingAssociation.AdditionalInfo);
if (additionalInfo != null && additionalInfo.ContainsKey("CreatedBy"))
{
var createdBy = additionalInfo["CreatedBy"]?.ToString();
isPreDiscoveryAssociation = createdBy == "PreDiscovery";
}
}
catch
{
// Ignora errori di parsing
}
}
Flusso di Decisione
┌─────────────────────────────────────┐
│ Esiste associazione attiva? │
└─────────────┬───────────────────────┘
│
├─ NO ──> Crea nuovo record
│
└─ SI ──> È da Pre-Discovery?
│
├─ SI ──> ✅ FORZA AGGIORNAMENTO (skip hash)
│
└─ NO ──> Controlla hash
│
├─ Hash identico ──> Skip record
│
└─ Hash diverso ──> Aggiorna record
Codice Modificato
File: Data_Coupler/Pages/DataCoupler.razor.cs
Metodo: StartDataTransferWithComposite() (linea ~2840-2890)
if (existingAssociation != null && existingAssociation.IsActive)
{
// Verifica se l'associazione è stata creata dal Pre-Discovery
var isPreDiscoveryAssociation = false;
if (!string.IsNullOrEmpty(existingAssociation.AdditionalInfo))
{
try
{
var additionalInfo = System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, object>>(existingAssociation.AdditionalInfo);
if (additionalInfo != null && additionalInfo.ContainsKey("CreatedBy"))
{
var createdBy = additionalInfo["CreatedBy"]?.ToString();
isPreDiscoveryAssociation = createdBy == "PreDiscovery";
}
}
catch
{
// Ignora errori di parsing
}
}
// 🔍 PRE-DISCOVERY: Se l'associazione è stata appena creata dal Pre-Discovery, FORZA l'aggiornamento
if (isPreDiscoveryAssociation)
{
// Forza aggiornamento senza controllo hash
recordsForUpdate.Add((restData, existingAssociation.DestinationId, record, recordNumber, currentDataHash));
Logger.LogInformation("COMPOSITE PARALLEL: Record {RecordNumber} marcato per AGGIORNAMENTO FORZATO (Pre-Discovery) - EntityId: {EntityId}",
recordNumber, existingAssociation.DestinationId);
}
else
{
// CONTROLLO HASH: Verifica se i dati sono cambiati (solo per associazioni esistenti)
var existingHash = existingAssociation.Data_Hash;
if (!string.IsNullOrEmpty(existingHash) && existingHash.Equals(currentDataHash, StringComparison.OrdinalIgnoreCase))
{
// I dati non sono cambiati, salta questo record
recordsSkipped.Add((record, recordNumber, "Dati non modificati (hash identico)"));
Logger.LogDebug("COMPOSITE PARALLEL: Record {RecordNumber} saltato - hash identico: {Hash}",
recordNumber, currentDataHash);
}
else
{
// I dati sono cambiati o l'hash è vuoto, procedi con l'aggiornamento
recordsForUpdate.Add((restData, existingAssociation.DestinationId, record, recordNumber, currentDataHash));
Logger.LogDebug("COMPOSITE PARALLEL: Record {RecordNumber} marcato per aggiornamento (EntityId: {EntityId}) - hash diverso: old={OldHash}, new={NewHash}",
recordNumber, existingAssociation.DestinationId, existingHash ?? "NULL", currentDataHash);
}
}
}
📊 Logging
Il sistema ora produce log specifici per aggiornamenti forzati:
COMPOSITE PARALLEL: Record 42 marcato per AGGIORNAMENTO FORZATO (Pre-Discovery) - EntityId: 003xxxxxxxxxxxxx
vs. aggiornamenti normali:
COMPOSITE PARALLEL: Record 42 marcato per aggiornamento (EntityId: 003xxxxxxxxxxxxx) - hash diverso: old=abc123, new=def456
🎯 Comportamento Atteso
Scenario 1: Prima Esecuzione con Pre-Discovery
Setup:
- ❌ Tabella
KeyAssociationsvuota - ✅ Record già presenti in Salesforce
Risultato:
- Pre-Discovery trova i record esistenti
- Crea associazioni con
CreatedBy: "PreDiscovery" - Forza l'aggiornamento di tutti i record trovati (ignora hash)
- Aggiorna
Data_Hashcon il nuovo valore - Record sincronizzati tra sorgente e destinazione
Scenario 2: Seconda Esecuzione (Associazioni Esistenti)
Setup:
- ✅ Tabella
KeyAssociationspopolata (dalla prima esecuzione) - ✅ Record già sincronizzati
Risultato:
- Trova associazioni esistenti (NON da Pre-Discovery)
- Controlla l'hash dei dati
- Se hash identico → Skip record (performance ottimizzata)
- Se hash diverso → Aggiorna record normalmente
Scenario 3: Record Modificato in Sorgente
Setup:
- ✅ Associazione esistente
- 🔄 Dati modificati in sorgente
Risultato:
- Trova associazione esistente
- Calcola nuovo hash:
new_hash ≠ old_hash - Aggiorna il record in destinazione
- Salva il nuovo hash
🔄 Ciclo di Vita Associazione Pre-Discovery
┌──────────────────────────────────────────────────────────────┐
│ 1° TRASFERIMENTO (KeyAssociations vuota) │
├──────────────────────────────────────────────────────────────┤
│ Pre-Discovery: Cerca record in Salesforce │
│ ✅ Trovato → Crea associazione con CreatedBy="PreDiscovery" │
│ 🔄 AGGIORNAMENTO FORZATO (skip hash check) │
│ 💾 Salva nuovo hash │
└──────────────────────────────────────────────────────────────┘
↓
┌──────────────────────────────────────────────────────────────┐
│ 2° TRASFERIMENTO (Associazione esistente) │
├──────────────────────────────────────────────────────────────┤
│ Trova associazione (CreatedBy="PreDiscovery") │
│ 🔍 CONTROLLO HASH │
│ • Hash identico → ⏭️ Skip (performance) │
│ • Hash diverso → 🔄 Aggiorna normalmente │
└──────────────────────────────────────────────────────────────┘
↓
┌──────────────────────────────────────────────────────────────┐
│ TRASFERIMENTI SUCCESSIVI │
├──────────────────────────────────────────────────────────────┤
│ Comportamento normale con controllo hash │
│ (l'associazione non è più "nuova" dal Pre-Discovery) │
└──────────────────────────────────────────────────────────────┘
⚠️ Note Importanti
Metodo Original (Non-Composite)
Il metodo StartDataTransferOriginal() non ha controllo hash. Tenta sempre l'aggiornamento direttamente, quindi non necessita di modifiche per il Pre-Discovery.
Performance
- Prima esecuzione: Tutti i record trovati dal Pre-Discovery vengono aggiornati forzatamente
- Esecuzioni successive: Controllo hash ottimizza le performance saltando record non modificati
Pulizia Marker
Il marker CreatedBy: "PreDiscovery" rimane nell'associazione per tutta la sua vita. Questo permette di:
- Tracciare l'origine dell'associazione
- Debugging più semplice
- Statistiche su quante associazioni sono state scoperte automaticamente
🧪 Test Case
Test 1: Pre-Discovery con Aggiornamento Forzato
SETUP:
- KeyAssociations: vuota
- Salesforce: Record con Email="test@example.com"
- Sorgente: Record con Email="test@example.com" + dati aggiornati
EXPECTED:
1. Pre-Discovery trova record in Salesforce
2. Crea associazione con CreatedBy="PreDiscovery"
3. FORZA aggiornamento (salta controllo hash)
4. Log: "AGGIORNAMENTO FORZATO (Pre-Discovery)"
5. Record aggiornato in Salesforce
Test 2: Secondo Trasferimento con Hash Check
SETUP:
- KeyAssociations: popolata (da Test 1)
- Sorgente: Dati NON modificati
EXPECTED:
1. Trova associazione esistente
2. Calcola hash: identico
3. Skip record (performance)
4. Log: "saltato - hash identico"
5. Nessuna chiamata API a Salesforce
Data Implementazione: 21 Ottobre 2025
File Modificato: Data_Coupler/Pages/DataCoupler.razor.cs
Metodo: StartDataTransferWithComposite()
Linee: ~2840-2890