## Bulk Pre-Discovery e riduzione query SQLite/SOQL
### KeyAssociationService — FindAssociationsByKeyValuesBulkAsync (nuovo)
- Aggiunta query bulk 'WHERE KeyValue IN (...)' per recuperare N associazioni con 1 sola query SQLite
(chunking a 500 chiavi per rispettare il limite ~999 parametri di SQLite)
- Aggiunta interfaccia IKeyAssociationService e delegata in DataConnectionCredentialService / IDataConnectionCredentialService
### AssociationService — BatchFindOrCreateAssociationsAsync (nuovo)
- Nuovo metodo bulk che sostituisce i loop per-record durante l'analisi composite:
1) 1 query SQLite bulk per tutte le chiavi
2) Per le chiavi non trovate: SOQL 'IN (...)' su Salesforce in chunk da 200 via BatchExecuteQueriesAsync
(ceil(K/25) HTTP Composite call invece di K singole)
3) Salvataggio parallelo delle associazioni pre-discovery scoperte
- Fallback per-record automatico per client REST non Salesforce
- Aggiornata interfaccia IAssociationService con documentazione XML completa
### DataCoupler.razor.cs — STEP A/B nel flusso COMPOSITE
- Pre-Discovery spostata FUORI dal loop parallelo (STEP A, prima dell'analisi)
- associationsByKey pre-popolato con BatchFindOrCreateAssociationsAsync
- STEP B: il loop parallelo usa TryGetValue O(1) invece di query async per record
- Rimozione blocco ~40 righe di per-record lookup / fallback duplicati
## Salesforce Composite API — Batch Delete e Patch
### SalesforceServiceClient — metodi batch (nuovi)
- BatchDeleteEntitiesAsync: elimina N record con ceil(N/25) Composite call invece di N
- BatchPatchSingleFieldAsync: aggiorna un singolo campo su N record tramite BatchUpdateEntitiesAsync
### DeletionSyncService — refactoring batch
- ExecuteBatchedSalesforceDeletionsAsync: orchestrazione batch per Delete / Deactivate / Mark su Salesforce
- ExecuteSequentialDeletionsAsync: loop sequenziale esistente estratto in metodo riutilizzabile
- Dispatcher: Salesforce -> batch Composite, altri client REST -> sequenziale
## Supporto OLE DB (database)
### DatabaseSchemaProviderFactory
- Aggiunto case DatabaseType.OleDb -> new OleDbSchemaProvider() nel factory switch
### DatabaseMethod.cs
- Aggiunto metodo IsOleDbConnection() (parallelo a IsOdbcConnection())
- Query validation e manager temporaneo estesi a OLE DB oltre che ODBC
- GetLimitedQuery: aggiunto case OleDb -> 'SELECT TOP N FROM (subquery)'
## Salesforce OAuth2 — fix client_credentials
### CredentialService.cs
- Aggiunto 'GrantType' alla HashSet serviceSpecificKeys per preservarlo nella serializzazione AdditionalParameters
### DataConnectionCredentialService.cs
- Refactored BuildRestServiceOptions in helper statico riutilizzato da entrambi i metodi GetRestServiceOptions
- Mapping coerente ClientId/ClientSecret/GrantType per Salesforce (allineato a DataConnectionFactory)
- TestSalesforceOAuthLogin: branch esplicito per client_credentials (no username/password/token)
con validazione preventiva ClientId+ClientSecret obbligatori
- Log flow label (password|client_credentials) in tutti i messaggi di autenticazione
## VS Code tasks
### .vscode/tasks.json
- Rimosso task generico 'Publish Data_Coupler'
- Aggiunti due task separati: win-x64 e win-x86, entrambi SingleFile + Self-Contained + ReadyToRun
Implementata la possibilita' di usare Salesforce come fonte dati e un database relazionale come destinazione nel flusso di trasferimento dati.
## Modifiche principali
### Nuovi file partial class
- Data_Coupler/Extensions/DataCoupler/SalesforceSourceMethod.cs
- Stato e metodi per Salesforce come fonte (credenziali, connessione, discovery SObject)
- ConnectToSalesforceSource(): autenticazione parallela + discovery summaries/details
- SelectSalesforceSourceEntity(): selezione SObject e caricamento campi
- GetAllRecordsFromSalesforceSource(): estrazione via ExtractAllEntitiesAsync con solo i campi mappati
- Filtro/ricerca SObject in tempo reale
- Data_Coupler/Extensions/DataCoupler/DatabaseDestinationMethod.cs
- Stato e metodi per database come destinazione
- ConnectToDestinationDatabase(): connessione e discovery tabelle
- SelectDestinationTable(): caricamento schema tabella on-demand
- IsDestinationDatabaseReady: proprieta' calcolata per validazione
- Toggle UI tra destinazione REST e Database
### IDatabaseManager interface
- Aggiunto UpsertRecordAsync(tableName, keyField, keyValue, record):
- Esegue SELECT COUNT(*) per verificare esistenza record
- UPDATE se esiste, INSERT se non esiste
- Implementato in EFCoreDatabaseManager (parametri named @p0..@pN)
- Implementato in OdbcDatabaseManager (parametri posizionali ?)
### DataCoupler.razor (UI)
- Aggiunto 'Salesforce (REST API)' nel dropdown tipo fonte
- Sezione UI Salesforce fonte: selettore credenziali, bottone connessione, lista SObject con ricerca
- Toggle destinazione REST/Database nella card destra
- Sezione UI Database destinazione: selettore credenziali, bottone connessione, lista tabelle con ricerca
- Colonna destra mapping aggiornata: mostra colonne DB se destinazione e' database, proprieta' REST altrimenti
- Colonna sinistra mapping: aggiunta sezione campi SObject Salesforce
- isSourceReady aggiornato per includere fonte Salesforce
- isDestinationReady aggiornato per includere destinazione database
- Etichette mapping dinamiche in base ai tipi selezionati
### DataCoupler.razor.cs (logica)
- LoadCredentials(): aggiunto caricamento credenziali Salesforce fonte
- ResetSourceState(): aggiunto reset stato Salesforce fonte
- ResetDestinationState(): aggiunto reset stato database destinazione
- GetAllRecordsFromSource(): aggiunto branch Salesforce
- StartDataTransfer(): routing verso StartDataTransferToDatabase() se dest=database
- Aggiunto StartDataTransferToDatabase(): estrae record fonte, applica mapping e default values, chiama UpsertRecordAsync per ogni record
- Rimosso codice duplicato in StartDataTransfer()
- Salesforce Composite Batch API per describe SObject: le describe sono ora
raggruppate in chunk da 25 e inviate come singole POST a /composite/batch,
riducendo le chiamate API da N a ceil(N/25); per 200 SObject: da 201 a 9 chiamate.
- Discovery entita' REST in parallelo: DiscoverEntitySummariesAsync e
DiscoverEntitiesAsync avviate simultaneamente; la lista entita' diventa
interattiva subito dopo le summaries, i dettagli completano in background
con StateHasChanged() per aggiornare l'UI istantaneamente.
- Fix scheduler - preservazione ExternalIdRelationshipsJson e DefaultValuesJson:
in DataCoupler.razor.cs entrambi i blocchi di update profilo esistente
(riattivazione profilo inattivo e sovrascrittura profilo attivo) omettevano
questi campi nella copia, causandone l'azzeramento silenzioso ad ogni
re-salvataggio. Ora entrambi i percorsi propagano correttamente i campi JSON.
- Fix scheduler - esclusione campi sorgente External ID dal mapping normale:
in ScheduledProfileExecutionService.TransformRecordForRest i campi sorgente
usati nelle External ID Relationships venivano inclusi anche nel loop di
field mapping standard, generando dati duplicati nell'entita' destinazione.
Ora il comportamento e' allineato alla UI manuale (TransformRecordToRestEntity).
- Aggiornata documentazione: README.md, AGENTS.md, copilot-instructions.md
- 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
- Modificato OnDatabaseCredentialChanged per rilevare connessioni ODBC e forzare useCustomQuery = true
- Aggiunto metodo helper IsOdbcConnection() per verificare tipo credenziale
- Modificata UI DataCoupler.razor:
* Nascosto pulsante 'Connetti e Scopri Schema' per ODBC
* Mostrato messaggio esplicativo per ODBC
* Resa sezione Query Custom sempre visibile per ODBC (senza discovery)
* Nascosta sezione Lista Tabelle per ODBC
- Modificato ValidateCustomQuery per creare temporaneamente DatabaseManager per ODBC
- ODBC ora bypassa completamente il discovery e va diretto a query custom
- Estrazione logica database in DatabaseMethod.cs con tutte le proprietà e metodi protected
- Estrazione logica REST API in RESTMethod.cs con gestione completa delle connessioni
- Creazione DataCouplerModels.cs per modelli condivisi (TransferResult)
- Pulizia file principale DataCoupler.razor.cs mantenendo solo orchestrazione generale
- Rimozione codice duplicato e miglioramento separazione delle responsabilità
- Compilazione verificata senza errori per l'intera soluzione
Struttura finale:
- DataCoupler.razor.cs: gestione file, profili, UI e coordinamento
- DatabaseMethod.cs: connessioni DB, query custom, discovery tabelle/schemi
- RESTMethod.cs: autenticazione REST, discovery entità, metadata
- DataCouplerModels.cs: modelli comuni per