[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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,6 +85,18 @@ public interface IDatabaseManager : IDisposable
|
||||
/// Ottiene il nome del campo Primary Key di una tabella specifica
|
||||
/// </summary>
|
||||
Task<string?> GetPrimaryKeyFieldAsync(string tableName);
|
||||
|
||||
/// <summary>
|
||||
/// Esegue un upsert (INSERT o UPDATE) di un singolo record nella tabella specificata.
|
||||
/// Se un record con lo stesso valore del campo chiave esiste già, viene aggiornato;
|
||||
/// altrimenti viene inserito un nuovo record.
|
||||
/// </summary>
|
||||
/// <param name="tableName">Nome della tabella di destinazione</param>
|
||||
/// <param name="keyField">Campo chiave per determinare se il record esiste</param>
|
||||
/// <param name="keyValue">Valore del campo chiave del record</param>
|
||||
/// <param name="record">Campi e valori da inserire/aggiornare</param>
|
||||
/// <returns>True se l'operazione è riuscita, false altrimenti</returns>
|
||||
Task<bool> UpsertRecordAsync(string tableName, string keyField, object? keyValue, Dictionary<string, object?> record);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -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