[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:
@@ -0,0 +1,238 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using CredentialManager.Models;
|
||||
using DataConnection.Interfaces;
|
||||
using Data_Coupler.Services;
|
||||
|
||||
namespace Data_Coupler.Pages;
|
||||
|
||||
/// <summary>
|
||||
/// Partial class per la gestione di un database come destinazione dati.
|
||||
/// Consente di selezionare un database di destinazione, scoprirne le tabelle
|
||||
/// e configurare il mapping dei campi verso la tabella di destinazione.
|
||||
/// </summary>
|
||||
public partial class DataCoupler : ComponentBase
|
||||
{
|
||||
// ===== PROPRIETÀ TIPO DESTINAZIONE =====
|
||||
|
||||
/// <summary>
|
||||
/// Tipo di destinazione: "rest" (default) oppure "database".
|
||||
/// Controlla quale sezione UI viene mostrata nella card di destra.
|
||||
/// </summary>
|
||||
protected string selectedDestinationType = "rest";
|
||||
|
||||
// ===== PROPRIETÀ DATABASE DESTINAZIONE =====
|
||||
|
||||
/// <summary>Credenziale database selezionata come destinazione</summary>
|
||||
protected string selectedDestinationDatabaseCredential = "";
|
||||
|
||||
/// <summary>Stato connessione in corso</summary>
|
||||
protected bool isConnectingDestinationDatabase = false;
|
||||
|
||||
/// <summary>Database di destinazione connesso con successo</summary>
|
||||
protected bool isDestinationDatabaseConnected = false;
|
||||
|
||||
/// <summary>Messaggio di errore connessione database destinazione</summary>
|
||||
protected string destinationDatabaseErrorMessage = "";
|
||||
|
||||
/// <summary>Nomi delle tabelle disponibili nel database di destinazione</summary>
|
||||
protected List<string> destAvailableTableNames = new();
|
||||
|
||||
/// <summary>Schema dettagliato per tabella di destinazione (caricato on-demand)</summary>
|
||||
protected Dictionary<string, IEnumerable<DbColumnInfo>> destDatabaseTables = new();
|
||||
|
||||
/// <summary>Tabella di destinazione selezionata</summary>
|
||||
protected string selectedDestinationTable = "";
|
||||
|
||||
/// <summary>Termine di ricerca per filtrare le tabelle di destinazione</summary>
|
||||
protected string destDatabaseSearchTerm = "";
|
||||
|
||||
/// <summary>Database manager per il database di destinazione</summary>
|
||||
protected IDatabaseManager? currentDestinationDatabaseManager = null;
|
||||
|
||||
// ===== METODI DATABASE DESTINAZIONE =====
|
||||
|
||||
/// <summary>
|
||||
/// Gestisce il cambio del tipo di destinazione (rest / database)
|
||||
/// </summary>
|
||||
protected void OnDestinationTypeChanged(ChangeEventArgs e)
|
||||
{
|
||||
var newType = e.Value?.ToString() ?? "rest";
|
||||
|
||||
if (newType == selectedDestinationType)
|
||||
return;
|
||||
|
||||
selectedDestinationType = newType;
|
||||
|
||||
// Reset lo stato della destinazione precedente
|
||||
ResetDestinationState();
|
||||
if (newType == "database")
|
||||
{
|
||||
ResetDestinationDatabaseState();
|
||||
}
|
||||
|
||||
// Pulisce i mapping configurati (dipendono dal tipo di destinazione)
|
||||
ClearAllMappings();
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gestisce il cambio della credenziale database di destinazione
|
||||
/// </summary>
|
||||
protected void OnDestinationDatabaseCredentialChanged(ChangeEventArgs e)
|
||||
{
|
||||
selectedDestinationDatabaseCredential = e.Value?.ToString() ?? "";
|
||||
ResetDestinationDatabaseState();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resetta lo stato del database di destinazione
|
||||
/// </summary>
|
||||
protected void ResetDestinationDatabaseState()
|
||||
{
|
||||
isDestinationDatabaseConnected = false;
|
||||
destAvailableTableNames.Clear();
|
||||
destDatabaseTables.Clear();
|
||||
selectedDestinationTable = "";
|
||||
destDatabaseSearchTerm = "";
|
||||
destinationDatabaseErrorMessage = "";
|
||||
|
||||
// Rilascia il database manager
|
||||
if (currentDestinationDatabaseManager != null)
|
||||
{
|
||||
try { currentDestinationDatabaseManager.Dispose(); } catch { /* ignore */ }
|
||||
currentDestinationDatabaseManager = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Si connette al database di destinazione e carica le tabelle disponibili
|
||||
/// </summary>
|
||||
protected async Task ConnectToDestinationDatabase()
|
||||
{
|
||||
if (string.IsNullOrEmpty(selectedDestinationDatabaseCredential))
|
||||
return;
|
||||
|
||||
isConnectingDestinationDatabase = true;
|
||||
destinationDatabaseErrorMessage = "";
|
||||
|
||||
try
|
||||
{
|
||||
// Verifica credenziale
|
||||
var credential = databaseCredentials.FirstOrDefault(c => c.Name == selectedDestinationDatabaseCredential);
|
||||
if (credential == null)
|
||||
{
|
||||
destinationDatabaseErrorMessage = "Credenziale database non trovata";
|
||||
return;
|
||||
}
|
||||
|
||||
// Crea il database manager
|
||||
currentDestinationDatabaseManager = await ConnectionFactory.CreateDatabaseManagerAsync(selectedDestinationDatabaseCredential);
|
||||
|
||||
// Verifica la connessione
|
||||
var canConnect = await currentDestinationDatabaseManager.TestConnectionAsync();
|
||||
if (!canConnect)
|
||||
{
|
||||
destinationDatabaseErrorMessage = "Impossibile connettersi al database di destinazione. Verificare le credenziali.";
|
||||
currentDestinationDatabaseManager.Dispose();
|
||||
currentDestinationDatabaseManager = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// Carica i nomi delle tabelle
|
||||
var tableNames = await currentDestinationDatabaseManager.GetTableNamesAsync();
|
||||
destAvailableTableNames = tableNames.OrderBy(t => t).ToList();
|
||||
isDestinationDatabaseConnected = true;
|
||||
|
||||
Logger.LogInformation("Database destinazione connesso: {Credential}, {Count} tabelle trovate",
|
||||
selectedDestinationDatabaseCredential, destAvailableTableNames.Count);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
destinationDatabaseErrorMessage = $"Errore di connessione: {ex.Message}";
|
||||
Logger.LogError(ex, "Errore nella connessione al database destinazione: {Credential}", selectedDestinationDatabaseCredential);
|
||||
|
||||
if (currentDestinationDatabaseManager != null)
|
||||
{
|
||||
try { currentDestinationDatabaseManager.Dispose(); } catch { /* ignore */ }
|
||||
currentDestinationDatabaseManager = null;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
isConnectingDestinationDatabase = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Seleziona la tabella di destinazione e carica il suo schema
|
||||
/// </summary>
|
||||
protected async Task SelectDestinationTable(string tableName)
|
||||
{
|
||||
selectedDestinationTable = tableName;
|
||||
|
||||
// Carica lo schema della tabella se non è già disponibile
|
||||
if (currentDestinationDatabaseManager != null && !destDatabaseTables.ContainsKey(tableName))
|
||||
{
|
||||
try
|
||||
{
|
||||
Logger.LogInformation("Caricamento schema tabella destinazione: {TableName}", tableName);
|
||||
var schema = await currentDestinationDatabaseManager.GetTableSchemaAsync(tableName);
|
||||
destDatabaseTables[tableName] = schema;
|
||||
Logger.LogInformation("Schema tabella destinazione caricato: {ColumnCount} colonne", schema.Count());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Errore nel caricamento schema tabella destinazione {TableName}", tableName);
|
||||
destinationDatabaseErrorMessage = $"Errore nel caricamento schema: {ex.Message}";
|
||||
}
|
||||
}
|
||||
|
||||
// Pulisce i mapping quando si cambia tabella
|
||||
ClearAllMappings();
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Restituisce la lista filtrata delle tabelle di destinazione
|
||||
/// </summary>
|
||||
protected IEnumerable<string> GetFilteredDestinationTables()
|
||||
{
|
||||
if (string.IsNullOrEmpty(destDatabaseSearchTerm))
|
||||
return destAvailableTableNames;
|
||||
|
||||
return destAvailableTableNames
|
||||
.Where(t => t.Contains(destDatabaseSearchTerm, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Aggiorna il termine di ricerca per le tabelle di destinazione
|
||||
/// </summary>
|
||||
protected void FilterDestinationTables(ChangeEventArgs e)
|
||||
{
|
||||
destDatabaseSearchTerm = e.Value?.ToString() ?? "";
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pulisce il termine di ricerca per le tabelle di destinazione
|
||||
/// </summary>
|
||||
protected void ClearDestinationTableSearch()
|
||||
{
|
||||
destDatabaseSearchTerm = "";
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indica se la configurazione corrente è pronta per il trasferimento verso database
|
||||
/// </summary>
|
||||
protected bool IsDestinationDatabaseReady =>
|
||||
isDestinationDatabaseConnected &&
|
||||
!string.IsNullOrEmpty(selectedDestinationTable) &&
|
||||
currentDestinationDatabaseManager != null;
|
||||
}
|
||||
Reference in New Issue
Block a user