[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:
@@ -579,4 +579,94 @@ public class EFCoreDatabaseManager : IDatabaseManager
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<bool> UpsertRecordAsync(string tableName, string keyField, object? keyValue, Dictionary<string, object?> record)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_context.Database.GetDbConnection().State != ConnectionState.Open)
|
||||
await _context.Database.OpenConnectionAsync();
|
||||
|
||||
var connection = _context.Database.GetDbConnection();
|
||||
|
||||
// Determina il riferimento alla tabella (con o senza schema)
|
||||
string tableRef;
|
||||
if (tableName.Contains('.'))
|
||||
{
|
||||
var parts = tableName.Split('.', 2);
|
||||
tableRef = $"[{parts[0]}].[{parts[1]}]";
|
||||
}
|
||||
else
|
||||
{
|
||||
tableRef = $"[{tableName}]";
|
||||
}
|
||||
|
||||
// Controlla se il record esiste già
|
||||
using var checkCmd = connection.CreateCommand();
|
||||
checkCmd.CommandText = $"SELECT COUNT(*) FROM {tableRef} WHERE [{keyField}] = @p0";
|
||||
var checkParam = checkCmd.CreateParameter();
|
||||
checkParam.ParameterName = "@p0";
|
||||
checkParam.Value = keyValue ?? DBNull.Value;
|
||||
checkCmd.Parameters.Add(checkParam);
|
||||
|
||||
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, i) => $"[{f}] = @p{i}").ToList();
|
||||
var updateSql = $"UPDATE {tableRef} SET {string.Join(", ", setClauses)} WHERE [{keyField}] = @p{setClauses.Count}";
|
||||
|
||||
using var updateCmd = connection.CreateCommand();
|
||||
updateCmd.CommandText = updateSql;
|
||||
|
||||
for (int i = 0; i < fields.Count; i++)
|
||||
{
|
||||
var p = updateCmd.CreateParameter();
|
||||
p.ParameterName = $"@p{i}";
|
||||
p.Value = record[fields[i]] ?? DBNull.Value;
|
||||
updateCmd.Parameters.Add(p);
|
||||
}
|
||||
|
||||
// Aggiunge il parametro per la WHERE
|
||||
var keyParam = updateCmd.CreateParameter();
|
||||
keyParam.ParameterName = $"@p{fields.Count}";
|
||||
keyParam.Value = keyValue ?? DBNull.Value;
|
||||
updateCmd.Parameters.Add(keyParam);
|
||||
|
||||
await updateCmd.ExecuteNonQueryAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
// INSERT
|
||||
var fields = record.Keys.ToList();
|
||||
var fieldNames = string.Join(", ", fields.Select(f => $"[{f}]"));
|
||||
var paramPlaceholders = string.Join(", ", fields.Select((_, i) => $"@p{i}"));
|
||||
var insertSql = $"INSERT INTO {tableRef} ({fieldNames}) VALUES ({paramPlaceholders})";
|
||||
|
||||
using var insertCmd = connection.CreateCommand();
|
||||
insertCmd.CommandText = insertSql;
|
||||
|
||||
for (int i = 0; i < fields.Count; i++)
|
||||
{
|
||||
var p = insertCmd.CreateParameter();
|
||||
p.ParameterName = $"@p{i}";
|
||||
p.Value = record[fields[i]] ?? DBNull.Value;
|
||||
insertCmd.Parameters.Add(p);
|
||||
}
|
||||
|
||||
await insertCmd.ExecuteNonQueryAsync();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Errore nell'upsert in {tableName}: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user