[Feature] Salesforce come fonte e Database come destinazione nel Data Coupler
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()
This commit is contained in:
@@ -97,6 +97,7 @@ public partial class DataCoupler : ComponentBase
|
||||
{
|
||||
databaseCredentials = await CredentialService.GetAllDatabaseCredentialsAsync();
|
||||
await LoadRestCredentials(); // Carica le credenziali REST dalla classe parziale
|
||||
await LoadSalesforceSourceCredentials(); // Carica le credenziali Salesforce per la fonte
|
||||
// Carica anche i profili disponibili
|
||||
await LoadProfiles();
|
||||
}
|
||||
@@ -809,6 +810,7 @@ public partial class DataCoupler : ComponentBase
|
||||
restSearchTerm = "";
|
||||
currentRestDiscovery = null;
|
||||
currentRestClient = null;
|
||||
ResetDestinationDatabaseState();
|
||||
}
|
||||
|
||||
private void OnSourceTypeChanged(ChangeEventArgs e)
|
||||
@@ -833,6 +835,9 @@ public partial class DataCoupler : ComponentBase
|
||||
fileData.Clear();
|
||||
selectedSheet = "";
|
||||
|
||||
// Reset Salesforce source state
|
||||
ResetSalesforceSourceState();
|
||||
|
||||
// Reset pagination
|
||||
currentPage = 1;
|
||||
|
||||
@@ -1766,25 +1771,103 @@ public partial class DataCoupler : ComponentBase
|
||||
}
|
||||
private async Task StartDataTransfer()
|
||||
{
|
||||
// Verifica se possiamo utilizzare le chiamate Composite (solo per Salesforce)
|
||||
// Se destinazione è database, usa il metodo dedicato
|
||||
if (selectedDestinationType == "database")
|
||||
{
|
||||
await StartDataTransferToDatabase();
|
||||
return;
|
||||
}
|
||||
|
||||
// Verifica se possiamo utilizzare le chiamate Composite (solo per Salesforce REST dest)
|
||||
if (currentRestClient is DataConnection.REST.Implementations.SalesforceServiceClient)
|
||||
{
|
||||
await StartDataTransferWithComposite();
|
||||
return;
|
||||
}
|
||||
|
||||
// Fallback al metodo originale per altri client REST
|
||||
// Se siamo con Salesforce, usa il nuovo metodo Composite
|
||||
if (currentRestClient is DataConnection.REST.Implementations.SalesforceServiceClient)
|
||||
{
|
||||
await StartDataTransferWithComposite();
|
||||
return;
|
||||
}
|
||||
|
||||
// Per altri client, usa il metodo originale
|
||||
// Per altri client REST, usa il metodo originale
|
||||
await StartDataTransferOriginal();
|
||||
}
|
||||
|
||||
private async Task StartDataTransferToDatabase()
|
||||
{
|
||||
if (!fieldMappings.Any() || currentDestinationDatabaseManager == null || string.IsNullOrEmpty(selectedDestinationTable))
|
||||
{
|
||||
transferMessage = "Configurazione incompleta. Assicurati di aver selezionato la fonte, la tabella di destinazione e configurato almeno un mapping.";
|
||||
transferMessageType = "error";
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(sourceKeyField))
|
||||
{
|
||||
transferMessage = "Seleziona un campo chiave sorgente per l'operazione di upsert.";
|
||||
transferMessageType = "error";
|
||||
return;
|
||||
}
|
||||
|
||||
isTransferringData = true;
|
||||
transferMessage = "";
|
||||
int successCount = 0;
|
||||
int errorCount = 0;
|
||||
StateHasChanged();
|
||||
|
||||
try
|
||||
{
|
||||
var sourceRecords = await GetAllRecordsFromSource();
|
||||
var recordsList = sourceRecords.ToList();
|
||||
transferMessage = $"Elaborazione di {recordsList.Count} record...";
|
||||
StateHasChanged();
|
||||
|
||||
foreach (var sourceRecord in recordsList)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Applica mapping
|
||||
var destRecord = new Dictionary<string, object?>();
|
||||
foreach (var mapping in fieldMappings)
|
||||
{
|
||||
if (sourceRecord.TryGetValue(mapping.Key, out var value))
|
||||
destRecord[mapping.Value] = value;
|
||||
}
|
||||
// Aggiungi default values
|
||||
foreach (var dv in defaultValues)
|
||||
{
|
||||
if (!destRecord.ContainsKey(dv.Key))
|
||||
destRecord[dv.Key] = dv.Value;
|
||||
}
|
||||
|
||||
// Determina chiave di destinazione
|
||||
string destKeyField = fieldMappings.TryGetValue(sourceKeyField, out var mapped) ? mapped : sourceKeyField;
|
||||
object? keyValue = sourceRecord.TryGetValue(sourceKeyField, out var kv) ? kv : null;
|
||||
|
||||
var ok = await currentDestinationDatabaseManager.UpsertRecordAsync(
|
||||
selectedDestinationTable, destKeyField, keyValue, destRecord);
|
||||
|
||||
if (ok) successCount++;
|
||||
else errorCount++;
|
||||
}
|
||||
catch
|
||||
{
|
||||
errorCount++;
|
||||
}
|
||||
}
|
||||
|
||||
transferMessage = $"Trasferimento completato: {successCount} record elaborati, {errorCount} errori.";
|
||||
transferMessageType = errorCount == 0 ? "success" : "warning";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
transferMessage = $"Errore durante il trasferimento: {ex.Message}";
|
||||
transferMessageType = "error";
|
||||
Logger.LogError(ex, "Errore nel trasferimento verso database");
|
||||
}
|
||||
finally
|
||||
{
|
||||
isTransferringData = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task StartDataTransferOriginal()
|
||||
{
|
||||
if (!fieldMappings.Any() || currentRestClient == null || selectedRestEntity == null)
|
||||
@@ -2248,6 +2331,10 @@ public partial class DataCoupler : ComponentBase
|
||||
{
|
||||
return await GetAllRecordsFromFile();
|
||||
}
|
||||
else if (selectedSourceType == "salesforce")
|
||||
{
|
||||
return await GetAllRecordsFromSalesforceSource();
|
||||
}
|
||||
|
||||
return new List<Dictionary<string, object>>();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user