[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:
2026-05-24 23:55:51 +02:00
parent b75e57fe31
commit 6452d45b77
8 changed files with 1091 additions and 41 deletions
@@ -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>
+57
View File
@@ -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;
}
}
}