[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:
@@ -350,4 +350,61 @@ public class OdbcDatabaseManager : IDatabaseManager
|
||||
{
|
||||
// Nessuna risorsa da rilasciare per ODBC diretto
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<bool> UpsertRecordAsync(string tableName, string keyField, object? keyValue, Dictionary<string, object?> record)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var connection = new OdbcConnection(_connectionString);
|
||||
await connection.OpenAsync();
|
||||
|
||||
// Controlla se il record esiste già (ODBC usa ? come placeholder)
|
||||
using var checkCmd = new OdbcCommand($"SELECT COUNT(*) FROM {tableName} WHERE [{keyField}] = ?", connection);
|
||||
checkCmd.Parameters.Add(new OdbcParameter { Value = keyValue ?? DBNull.Value });
|
||||
|
||||
var countResult = await checkCmd.ExecuteScalarAsync();
|
||||
bool exists = Convert.ToInt64(countResult ?? 0L) > 0;
|
||||
|
||||
if (exists)
|
||||
{
|
||||
// UPDATE
|
||||
var fields = record.Keys.ToList();
|
||||
var setClauses = fields.Select(f => $"[{f}] = ?").ToList();
|
||||
var updateSql = $"UPDATE {tableName} SET {string.Join(", ", setClauses)} WHERE [{keyField}] = ?";
|
||||
|
||||
using var updateCmd = new OdbcCommand(updateSql, connection);
|
||||
|
||||
foreach (var f in fields)
|
||||
updateCmd.Parameters.Add(new OdbcParameter { Value = record[f] ?? DBNull.Value });
|
||||
|
||||
// Parametro per la WHERE
|
||||
updateCmd.Parameters.Add(new OdbcParameter { Value = keyValue ?? DBNull.Value });
|
||||
|
||||
await updateCmd.ExecuteNonQueryAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
// INSERT
|
||||
var fields = record.Keys.ToList();
|
||||
var fieldNames = string.Join(", ", fields.Select(f => $"[{f}]"));
|
||||
var paramPlaceholders = string.Join(", ", fields.Select(_ => "?"));
|
||||
var insertSql = $"INSERT INTO {tableName} ({fieldNames}) VALUES ({paramPlaceholders})";
|
||||
|
||||
using var insertCmd = new OdbcCommand(insertSql, connection);
|
||||
|
||||
foreach (var f in fields)
|
||||
insertCmd.Parameters.Add(new OdbcParameter { Value = record[f] ?? DBNull.Value });
|
||||
|
||||
await insertCmd.ExecuteNonQueryAsync();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Errore nell'upsert ODBC in {tableName}: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user