feat: Implementato sistema Pre-Discovery con supporto esecuzioni schedulate
- 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
This commit is contained in:
@@ -504,6 +504,7 @@ namespace DataConnection.REST.Implementations
|
||||
}
|
||||
} /// <summary>
|
||||
/// Finds entities by their key fields in Salesforce.
|
||||
/// Uses External ID GET when possible (single field), otherwise uses SOQL query.
|
||||
/// </summary>
|
||||
/// <param name="entityName">The name of the SObject to search (e.g., "Account", "Contact").</param>
|
||||
/// <param name="keyFields">The key fields and their values to match.</param>
|
||||
@@ -522,40 +523,127 @@ namespace DataConnection.REST.Implementations
|
||||
return new List<Dictionary<string, object>>();
|
||||
}
|
||||
|
||||
// Costruisci la query SOQL
|
||||
var whereConditions = keyFields.Select(kvp =>
|
||||
// 🔍 IMPORTANTE: L'approccio External ID GET funziona SOLO per campi marcati come External ID in Salesforce
|
||||
// Per la maggior parte dei campi, è più affidabile usare direttamente SOQL query
|
||||
// Se vuoi abilitare il tentativo External ID, decommenta il blocco sotto
|
||||
|
||||
/* EXTERNAL ID GET - DISABILITATO PER DEFAULT
|
||||
if (keyFields.Count == 1)
|
||||
{
|
||||
var value = kvp.Value?.ToString() ?? "";
|
||||
// Se il valore è una stringa, aggiungi le virgolette
|
||||
if (kvp.Value is string)
|
||||
var kvp = keyFields.First();
|
||||
var fieldName = kvp.Key;
|
||||
var fieldValue = kvp.Value?.ToString() ?? "";
|
||||
|
||||
try
|
||||
{
|
||||
// Tentativo 1: GET con External ID (funziona SOLO per campi External ID)
|
||||
// Endpoint: /sobjects/{objectType}/{fieldName}/{fieldValue}
|
||||
var externalIdEndpoint = $"{_instanceUrl}/services/data/v60.0/sobjects/{entityName}/{fieldName}/{Uri.EscapeDataString(fieldValue)}";
|
||||
Console.WriteLine($"⚠️ Tentativo GET con External ID: {externalIdEndpoint}");
|
||||
Console.WriteLine($"⚠️ NOTA: Questo funziona SOLO se '{fieldName}' è marcato come External ID in Salesforce");
|
||||
|
||||
var getResponse = await _httpClient.GetAsync(externalIdEndpoint, cancellationToken);
|
||||
|
||||
if (getResponse.IsSuccessStatusCode)
|
||||
{
|
||||
var entity = await getResponse.Content.ReadFromJsonAsync<Dictionary<string, object>>(cancellationToken: cancellationToken);
|
||||
if (entity != null)
|
||||
{
|
||||
Console.WriteLine($"✅ Trovato tramite External ID GET: Id={entity.GetValueOrDefault("Id")}");
|
||||
return new List<Dictionary<string, object>> { entity };
|
||||
}
|
||||
}
|
||||
else if (getResponse.StatusCode == System.Net.HttpStatusCode.NotFound)
|
||||
{
|
||||
Console.WriteLine($"⚠️ External ID GET ha restituito 404 - Il campo '{fieldName}' probabilmente non è External ID");
|
||||
Console.WriteLine($" Uso SOQL query come fallback (metodo universale)");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"External ID GET non disponibile (Status: {getResponse.StatusCode}), uso SOQL query");
|
||||
}
|
||||
}
|
||||
catch (Exception externalIdEx)
|
||||
{
|
||||
Console.WriteLine($"External ID GET fallito: {externalIdEx.Message}, uso SOQL query");
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// 🔍 SOQL Query: metodo universale che funziona per TUTTI i campi (External ID o no)
|
||||
Console.WriteLine("📋 Usando SOQL Query per la ricerca (metodo universale)...");
|
||||
|
||||
// Costruisci le condizioni WHERE
|
||||
var whereConditions = new List<string>();
|
||||
foreach (var kvp in keyFields)
|
||||
{
|
||||
var fieldName = kvp.Key;
|
||||
var fieldValue = kvp.Value;
|
||||
|
||||
Console.WriteLine($" 🔎 Ricerca per campo: {fieldName} = {fieldValue}");
|
||||
|
||||
var value = fieldValue?.ToString() ?? "";
|
||||
|
||||
// Se il valore è una stringa, aggiungi le virgolette ed escape
|
||||
if (fieldValue is string)
|
||||
{
|
||||
value = $"'{value.Replace("'", "\\'")}'"; // Escape delle virgolette
|
||||
}
|
||||
return $"{kvp.Key} = {value}";
|
||||
});
|
||||
|
||||
whereConditions.Add($"{fieldName} = {value}");
|
||||
}
|
||||
|
||||
var query = $"SELECT Id FROM {entityName} WHERE {string.Join(" AND ", whereConditions)}";
|
||||
Console.WriteLine($"SOQL Query: {query}");
|
||||
|
||||
var encodedQuery = Uri.EscapeDataString(query);
|
||||
var queryEndpoint = $"/services/data/v59.0/query/?q={encodedQuery}"; var response = await GetAsync<SalesforceQueryResponse>(queryEndpoint, cancellationToken);
|
||||
// Costruisci la query: seleziona tutti i campi forniti + Id
|
||||
var fieldsToSelect = new List<string> { "Id" };
|
||||
fieldsToSelect.AddRange(keyFields.Keys.Where(k => k != "Id"));
|
||||
|
||||
if (response?.Records != null)
|
||||
var query = $"SELECT {string.Join(", ", fieldsToSelect)} FROM {entityName} WHERE {string.Join(" AND ", whereConditions)}";
|
||||
Console.WriteLine($"📝 SOQL Query: {query}");
|
||||
|
||||
// Usa l'endpoint query con autenticazione corretta
|
||||
var encodedQuery = Uri.EscapeDataString(query);
|
||||
var queryEndpoint = $"{_instanceUrl}/services/data/v60.0/query/?q={encodedQuery}";
|
||||
|
||||
Console.WriteLine($"Query Endpoint: {queryEndpoint}");
|
||||
|
||||
// Usa GET con autenticazione inclusa nell'HttpClient (già configurato)
|
||||
var response = await _httpClient.GetAsync(queryEndpoint, cancellationToken);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
var results = response.Records.Select(record =>
|
||||
var errorContent = await response.Content.ReadAsStringAsync(cancellationToken);
|
||||
Console.WriteLine($"SOQL Query failed: {response.StatusCode}");
|
||||
Console.WriteLine($"Error details: {errorContent}");
|
||||
return new List<Dictionary<string, object>>();
|
||||
}
|
||||
|
||||
var responseContent = await response.Content.ReadAsStringAsync(cancellationToken);
|
||||
Console.WriteLine($"SOQL Response: {responseContent}");
|
||||
|
||||
var queryResponse = JsonSerializer.Deserialize<SalesforceQueryResponse>(responseContent, SalesforceJsonOptions);
|
||||
|
||||
if (queryResponse?.Records != null && queryResponse.Records.Any())
|
||||
{
|
||||
var results = queryResponse.Records.Select(record =>
|
||||
record as Dictionary<string, object> ?? new Dictionary<string, object>()
|
||||
).ToList();
|
||||
|
||||
Console.WriteLine($"Found {results.Count} entities matching the key fields");
|
||||
Console.WriteLine($"✅ Trovati {results.Count} record tramite SOQL");
|
||||
foreach (var result in results)
|
||||
{
|
||||
Console.WriteLine($" - Id: {result.GetValueOrDefault("Id")}, Campi: {string.Join(", ", result.Keys)}");
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
Console.WriteLine("No entities found matching the key fields");
|
||||
Console.WriteLine("Nessun record trovato");
|
||||
return new List<Dictionary<string, object>>();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error during Salesforce entity search: {ex.Message}");
|
||||
Console.WriteLine($"❌ Errore durante la ricerca Salesforce: {ex.Message}");
|
||||
Console.WriteLine($"Stack Trace: {ex.StackTrace}");
|
||||
return new List<Dictionary<string, object>>();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user