Files
Data-Coupler/Data_Coupler/Extensions/DataCoupler/DatabaseMethod.cs
T
Alessio Dal Santo 344853fde9 [Feature/Perf] Ottimizzazioni bulk pre-discovery, batch deletion sync e supporto OLE DB / Salesforce client_credentials
## Bulk Pre-Discovery e riduzione query SQLite/SOQL

### KeyAssociationService — FindAssociationsByKeyValuesBulkAsync (nuovo)
- Aggiunta query bulk 'WHERE KeyValue IN (...)' per recuperare N associazioni con 1 sola query SQLite
  (chunking a 500 chiavi per rispettare il limite ~999 parametri di SQLite)
- Aggiunta interfaccia IKeyAssociationService e delegata in DataConnectionCredentialService / IDataConnectionCredentialService

### AssociationService — BatchFindOrCreateAssociationsAsync (nuovo)
- Nuovo metodo bulk che sostituisce i loop per-record durante l'analisi composite:
  1) 1 query SQLite bulk per tutte le chiavi
  2) Per le chiavi non trovate: SOQL 'IN (...)' su Salesforce in chunk da 200 via BatchExecuteQueriesAsync
     (ceil(K/25) HTTP Composite call invece di K singole)
  3) Salvataggio parallelo delle associazioni pre-discovery scoperte
- Fallback per-record automatico per client REST non Salesforce
- Aggiornata interfaccia IAssociationService con documentazione XML completa

### DataCoupler.razor.cs — STEP A/B nel flusso COMPOSITE
- Pre-Discovery spostata FUORI dal loop parallelo (STEP A, prima dell'analisi)
- associationsByKey pre-popolato con BatchFindOrCreateAssociationsAsync
- STEP B: il loop parallelo usa TryGetValue O(1) invece di query async per record
- Rimozione blocco ~40 righe di per-record lookup / fallback duplicati

## Salesforce Composite API — Batch Delete e Patch

### SalesforceServiceClient — metodi batch (nuovi)
- BatchDeleteEntitiesAsync: elimina N record con ceil(N/25) Composite call invece di N
- BatchPatchSingleFieldAsync: aggiorna un singolo campo su N record tramite BatchUpdateEntitiesAsync

### DeletionSyncService — refactoring batch
- ExecuteBatchedSalesforceDeletionsAsync: orchestrazione batch per Delete / Deactivate / Mark su Salesforce
- ExecuteSequentialDeletionsAsync: loop sequenziale esistente estratto in metodo riutilizzabile
- Dispatcher: Salesforce -> batch Composite, altri client REST -> sequenziale

## Supporto OLE DB (database)

### DatabaseSchemaProviderFactory
- Aggiunto case DatabaseType.OleDb -> new OleDbSchemaProvider() nel factory switch

### DatabaseMethod.cs
- Aggiunto metodo IsOleDbConnection() (parallelo a IsOdbcConnection())
- Query validation e manager temporaneo estesi a OLE DB oltre che ODBC
- GetLimitedQuery: aggiunto case OleDb -> 'SELECT TOP N FROM (subquery)'

## Salesforce OAuth2 — fix client_credentials

### CredentialService.cs
- Aggiunto 'GrantType' alla HashSet serviceSpecificKeys per preservarlo nella serializzazione AdditionalParameters

### DataConnectionCredentialService.cs
- Refactored BuildRestServiceOptions in helper statico riutilizzato da entrambi i metodi GetRestServiceOptions
- Mapping coerente ClientId/ClientSecret/GrantType per Salesforce (allineato a DataConnectionFactory)
- TestSalesforceOAuthLogin: branch esplicito per client_credentials (no username/password/token)
  con validazione preventiva ClientId+ClientSecret obbligatori
- Log flow label (password|client_credentials) in tutti i messaggi di autenticazione

## VS Code tasks

### .vscode/tasks.json
- Rimosso task generico 'Publish Data_Coupler'
- Aggiunti due task separati: win-x64 e win-x86, entrambi SingleFile + Self-Contained + ReadyToRun
2026-05-28 11:15:18 +02:00

947 lines
36 KiB
C#

using System;
using System.Data;
using System.Text;
using CredentialManager.Models;
using CredentialManager.Services;
using DataConnection.Interfaces;
using DataConnection.REST.Interfaces;
using DataConnection.REST.Models;
using DataConnection.CredentialManagement.Interfaces;
using ExcelDataReader;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.JSInterop;
using Microsoft.Extensions.Logging;
using Data_Coupler.Services;
using Data_Coupler.Models;
namespace Data_Coupler.Pages;
public partial class DataCoupler : ComponentBase
{
// ===== PROPRIETÀ DATABASE =====
// Stato delle credenziali database
protected List<DatabaseCredential> databaseCredentials = new();
protected string selectedDatabaseCredential = "";
// Stato connessioni database
protected bool isConnectingDatabase = false;
protected bool isDatabaseConnected = false;
protected string databaseErrorMessage = "";
// Database discovery
protected List<string> availableTableNames = new(); // Solo nomi delle tabelle
protected Dictionary<string, IEnumerable<DbColumnInfo>> databaseTables = new(); // Schema dettagliato per tabelle caricate
protected string selectedTable = "";
protected string databaseSearchTerm = "";
// Database selection - per gestire la selezione del database quando non specificato nella connection string
protected List<string> availableDatabases = new();
protected string selectedDatabase = "";
protected bool showDatabaseSelectionModal = false;
protected bool isLoadingDatabases = false;
// Database selection (schemas only)
protected List<string> availableSchemas = new();
protected string selectedSchema = "";
protected bool showSchemaSelectionModal = false;
protected bool isLoadingSchemas = false;
// Custom query functionality
protected bool useCustomQuery = false;
protected string customQuery = "";
protected bool isValidatingQuery = false;
protected bool isQueryValid = false;
protected string queryValidationMessage = "";
protected List<Dictionary<string, object>> queryPreviewData = new();
protected List<string> queryColumns = new();
protected bool showQueryPreview = false;
protected bool isLoadingPreview = false;
// Gestione chiavi sorgente per database
protected string suggestedPrimaryKey = ""; // Campo PK suggerito per database
// Servizi database
protected IDatabaseManager? currentDatabaseManager = null;
// ===== METODI DATABASE =====
/// <summary>
/// Verifica se la credenziale database selezionata è di tipo ODBC
/// </summary>
protected bool IsOdbcConnection()
{
if (string.IsNullOrEmpty(selectedDatabaseCredential))
return false;
var credential = databaseCredentials.FirstOrDefault(c => c.Name == selectedDatabaseCredential);
return credential?.DatabaseType == DatabaseType.Odbc;
}
/// <summary>
/// Verifica se la credenziale database selezionata è di tipo OLE DB
/// </summary>
protected bool IsOleDbConnection()
{
if (string.IsNullOrEmpty(selectedDatabaseCredential))
return false;
var credential = databaseCredentials.FirstOrDefault(c => c.Name == selectedDatabaseCredential);
return credential?.DatabaseType == DatabaseType.OleDb;
}
/// <summary>
/// Gestisce il cambio di credenziale database selezionata
/// </summary>
protected void OnDatabaseCredentialChanged(ChangeEventArgs e)
{
selectedDatabaseCredential = e.Value?.ToString() ?? "";
ResetDatabaseState();
// Se è una connessione ODBC, forza l'uso di query custom
if (IsOdbcConnection())
{
useCustomQuery = true;
}
}
/// <summary>
/// Resetta lo stato del database
/// </summary>
protected void ResetDatabaseState()
{
isDatabaseConnected = false;
databaseTables.Clear();
selectedTable = "";
databaseSearchTerm = "";
databaseErrorMessage = "";
// Reset database selection
availableDatabases.Clear();
selectedDatabase = "";
showDatabaseSelectionModal = false;
isLoadingDatabases = false;
// Reset custom query state
useCustomQuery = false;
customQuery = "";
isValidatingQuery = false;
isQueryValid = false;
queryValidationMessage = "";
queryPreviewData.Clear();
queryColumns.Clear();
showQueryPreview = false;
isLoadingPreview = false;
currentDatabaseManager?.Dispose();
currentDatabaseManager = null;
// Clear mappings when resetting database state
ClearAllMappings();
}
/// <summary>
/// Connette al database utilizzando le credenziali selezionate
/// </summary>
protected async Task ConnectToDatabase()
{
if (string.IsNullOrEmpty(selectedDatabaseCredential))
return;
isConnectingDatabase = true;
databaseErrorMessage = "";
try
{
// Trova la credenziale
var credential = databaseCredentials.FirstOrDefault(c => c.Name == selectedDatabaseCredential);
if (credential == null)
{
databaseErrorMessage = "Credenziale database non trovata";
return;
}
// Test della connessione
var (success, message) = await CredentialService.TestDatabaseConnectionAsync(credential.Name);
if (!success)
{
databaseErrorMessage = $"Connessione fallita: {message}";
return;
}
// Crea il database manager usando il factory con le credenziali complete
Logger.LogInformation("Creando database manager per credenziale: {CredentialName}", selectedDatabaseCredential);
currentDatabaseManager = await ConnectionFactory.CreateDatabaseManagerAsync(selectedDatabaseCredential);
Logger.LogInformation("Database manager creato con successo");
// Verifica se il database è specificato nella connection string
bool isDatabaseSpecified = await IsDatabaseSpecifiedInConnectionString(credential);
if (isDatabaseSpecified)
{
Logger.LogInformation("Database specificato nella connection string. Procedendo con discovery tabelle.");
try
{
await LoadTablesFromConnectedDatabase();
isDatabaseConnected = true;
Logger.LogInformation("Tabelle caricate con successo, database connesso");
return; // Importante: usciamo qui se tutto va bene
}
catch (Exception ex)
{
Logger.LogError(ex, "Errore nel caricamento tabelle dal database specificato");
databaseErrorMessage = $"Errore nel caricamento tabelle: {ex.Message}";
return;
}
}
else
{
Logger.LogInformation("Database non specificato nella connection string. Caricando database disponibili.");
await LoadAvailableDatabases();
if (availableDatabases.Any())
{
Logger.LogInformation("Trovati {DatabaseCount} database disponibili", availableDatabases.Count);
showDatabaseSelectionModal = true;
StateHasChanged();
return; // Non procediamo fino alla selezione del database
}
else
{
databaseErrorMessage = "Nessun database disponibile trovato";
return;
}
}
}
catch (Exception ex)
{
Logger.LogError(ex, "Errore nella connessione al database");
databaseErrorMessage = $"Errore: {ex.Message}";
}
finally
{
isConnectingDatabase = false;
StateHasChanged();
}
}
/// <summary>
/// Connette a un database specifico
/// </summary>
protected async Task ConnectToDatabaseWithSpecificDatabase(string databaseName)
{
if (string.IsNullOrEmpty(selectedDatabaseCredential))
return;
isConnectingDatabase = true;
databaseErrorMessage = "";
try
{
// Trova la credenziale
var credential = databaseCredentials.FirstOrDefault(c => c.Name == selectedDatabaseCredential);
if (credential == null)
{
databaseErrorMessage = "Credenziale database non trovata";
return;
}
// Test della connessione
var (success, message) = await CredentialService.TestDatabaseConnectionAsync(credential.Name);
if (!success)
{
databaseErrorMessage = $"Connessione fallita: {message}";
return;
}
// Crea il database manager
Logger.LogInformation("Creando database manager per credenziale: {CredentialName}", selectedDatabaseCredential);
currentDatabaseManager = await ConnectionFactory.CreateDatabaseManagerAsync(selectedDatabaseCredential);
Logger.LogInformation("Database manager creato con successo");
if (currentDatabaseManager == null)
{
databaseErrorMessage = "Database manager non disponibile";
return;
}
Logger.LogInformation("Cambiando database a: {DatabaseName}", databaseName);
// Usa il metodo dell'interfaccia per cambiare database
await currentDatabaseManager.ChangeDatabaseAsync(databaseName);
Logger.LogInformation("Database cambiato con successo a: {DatabaseName}", databaseName);
// Carica le tabelle dal database selezionato
await LoadTablesFromConnectedDatabase();
isDatabaseConnected = true;
Logger.LogInformation("Connessione completata per database: {DatabaseName}", databaseName);
}
catch (Exception ex)
{
Logger.LogError(ex, "Errore nella connessione al database specifico: {DatabaseName}", databaseName);
databaseErrorMessage = $"Errore nella connessione al database {databaseName}: {ex.Message}";
throw;
}
finally
{
isConnectingDatabase = false;
StateHasChanged();
}
}
/// <summary>
/// Seleziona una tabella database e carica il suo schema
/// </summary>
protected async Task SelectTable(string tableName)
{
selectedTable = tableName;
// Clear custom query state when selecting a table
useCustomQuery = false;
customQuery = "";
isQueryValid = false;
queryValidationMessage = "";
queryPreviewData.Clear();
queryColumns.Clear();
showQueryPreview = false;
// Clear mappings when changing table
ClearAllMappings();
// Reset key field logic
sourceKeyField = "";
suggestedPrimaryKey = "";
requiresManualKeySelection = false;
// Carica i dettagli della tabella se non sono già stati caricati
if (!databaseTables.ContainsKey(tableName) && currentDatabaseManager != null)
{
try
{
var tableSchema = await currentDatabaseManager.GetTableSchemaAsync(tableName);
databaseTables[tableName] = tableSchema;
}
catch (Exception ex)
{
Logger.LogError(ex, "Errore nel caricamento dello schema della tabella {TableName}", tableName);
databaseErrorMessage = $"Errore nel caricamento della tabella: {ex.Message}";
}
}
// If it's a database source, try to detect the primary key
if (selectedSourceType == "database" && currentDatabaseManager != null)
{
try
{
var primaryKey = await currentDatabaseManager.GetPrimaryKeyFieldAsync(tableName);
if (!string.IsNullOrEmpty(primaryKey))
{
suggestedPrimaryKey = primaryKey;
// AUTO-SELECT: Imposta automaticamente il campo chiave se rilevato
sourceKeyField = primaryKey;
requiresManualKeySelection = false;
Logger.LogInformation("Chiave primaria rilevata e auto-selezionata per la tabella {TableName}: {PrimaryKey}", tableName, primaryKey);
}
else
{
// No primary key found, require manual selection
requiresManualKeySelection = true;
sourceKeyField = "";
Logger.LogInformation("Nessuna chiave primaria trovata per la tabella {TableName}, selezione manuale richiesta", tableName);
}
}
catch (Exception ex)
{
Logger.LogError(ex, "Errore nel rilevamento della chiave primaria per la tabella {TableName}", tableName);
requiresManualKeySelection = true;
sourceKeyField = "";
}
}
else
{
// For non-database sources, always require manual selection
requiresManualKeySelection = true;
sourceKeyField = "";
}
StateHasChanged();
}
/// <summary>
/// Ottiene le tabelle filtrate in base al termine di ricerca
/// </summary>
protected IEnumerable<string> GetFilteredDatabaseTables()
{
if (string.IsNullOrEmpty(databaseSearchTerm))
return availableTableNames;
return availableTableNames.Where(table =>
table.Contains(databaseSearchTerm, StringComparison.OrdinalIgnoreCase));
}
/// <summary>
/// Filtra le tabelle database in base al termine di ricerca
/// </summary>
protected async Task FilterDatabaseTables(ChangeEventArgs e)
{
databaseSearchTerm = e.Value?.ToString() ?? "";
await InvokeAsync(StateHasChanged);
}
/// <summary>
/// Pulisce il filtro di ricerca delle tabelle database
/// </summary>
protected async Task ClearDatabaseSearch()
{
databaseSearchTerm = "";
await InvokeAsync(StateHasChanged);
}
/// <summary>
/// Seleziona una colonna database per il mapping
/// </summary>
protected void SelectDbColumn(string columnName)
{
selectedDbColumn = columnName;
}
/// <summary>
/// Verifica se il database è specificato nella connection string
/// </summary>
protected Task<bool> IsDatabaseSpecifiedInConnectionString(DatabaseCredential credential)
{
try
{
Logger.LogInformation("Verifica database specificato - Tipo: {DatabaseType}, DatabaseName: '{DatabaseName}', Connection: {ConnectionString}",
credential.DatabaseType, credential.DatabaseName, credential.ConnectionString?.Substring(0, Math.Min(100, credential.ConnectionString?.Length ?? 0)));
// Prima verifica se c'è un database specificato nel campo DatabaseName della credenziale
if (!string.IsNullOrEmpty(credential.DatabaseName))
{
Logger.LogInformation("Database specificato nel campo DatabaseName: '{DatabaseName}' - RESULT: TRUE", credential.DatabaseName);
return Task.FromResult(true);
}
// Per SQL Server verifica se Initial Catalog o Database è specificato nella connection string
if (credential.DatabaseType == DatabaseType.SqlServer)
{
var connectionString = credential.ConnectionString ?? "";
var hasInitialCatalog = connectionString.Contains("Initial Catalog=", StringComparison.OrdinalIgnoreCase);
var hasDatabase = connectionString.Contains("Database=", StringComparison.OrdinalIgnoreCase);
var result = hasInitialCatalog || hasDatabase;
Logger.LogInformation("SQL Server - HasInitialCatalog: {HasInitialCatalog}, HasDatabase: {HasDatabase}, Result: {Result}",
hasInitialCatalog, hasDatabase, result);
return Task.FromResult(result);
}
// TODO: Implementare per altri tipi di database
// MySQL: Database=
// PostgreSQL: Database=
// Oracle: più complesso con SID/Service Name
Logger.LogWarning("Verifica database specificato non implementata per tipo database: {DatabaseType}", credential.DatabaseType);
return Task.FromResult(true); // Default: assume database specificato per tipi non implementati
}
catch (Exception ex)
{
Logger.LogError(ex, "Errore nella verifica database specificato in connection string");
return Task.FromResult(true); // Default: assume database specificato in caso di errore
}
}
/// <summary>
/// Carica le tabelle dal database attualmente connesso
/// </summary>
protected async Task LoadTablesFromConnectedDatabase()
{
try
{
if (currentDatabaseManager == null)
{
databaseErrorMessage = "Database manager non disponibile";
return;
}
Logger.LogInformation("Caricando tabelle dal database connesso");
var tableNames = await currentDatabaseManager.GetTableNamesAsync();
availableTableNames = tableNames.ToList();
Logger.LogInformation("Caricate {Count} tabelle dal database", availableTableNames.Count);
// Resetta i dettagli delle tabelle - verranno caricati solo quando selezionati
databaseTables.Clear();
}
catch (Exception ex)
{
Logger.LogError(ex, "Errore nel caricamento delle tabelle dal database connesso");
databaseErrorMessage = $"Errore nel caricamento tabelle: {ex.Message}";
throw;
}
}
/// <summary>
/// Carica la lista dei database disponibili dal server
/// </summary>
protected async Task LoadAvailableDatabases()
{
try
{
if (currentDatabaseManager == null)
{
databaseErrorMessage = "Database manager non disponibile";
return;
}
isLoadingDatabases = true;
Logger.LogInformation("Caricando database disponibili");
// Usa il metodo corretto dell'interfaccia IDatabaseManager
var allDatabases = await currentDatabaseManager.GetAvailableDatabasesAsync();
Logger.LogInformation("Ottenuti {DatabaseCount} database dal server", allDatabases.Count);
// Filtra i database di sistema
availableDatabases = FilterSystemDatabases(allDatabases).ToList();
Logger.LogInformation("Trovati {TotalDatabases} database, filtrati a {FilteredDatabases} (esclusi quelli di sistema)",
allDatabases.Count, availableDatabases.Count);
}
catch (Exception ex)
{
Logger.LogError(ex, "Errore nel caricamento dei database disponibili");
databaseErrorMessage = $"Errore nel caricamento database: {ex.Message}";
}
finally
{
isLoadingDatabases = false;
}
}
/// <summary>
/// Tenta di auto-selezionare una chiave per le query custom
/// </summary>
protected void TryAutoSelectKeyForQuery(List<string> columns)
{
try
{
// Reset stato chiave
sourceKeyField = "";
suggestedPrimaryKey = "";
requiresManualKeySelection = true;
// Pattern comuni per identificare possibili chiavi primarie
var keyPatterns = new[]
{
"id", "ID", "Id",
"_id", "_ID", "_Id",
"key", "KEY", "Key",
"code", "CODE", "Code", "codice", "CODICE", "Codice",
"number", "NUMBER", "Number", "numero", "NUMERO", "Numero",
"index", "INDEX", "Index", "indice", "INDICE", "Indice"
};
// Cerca colonne che potrebbero essere chiavi primarie
string? detectedKey = null;
// 1. Cerca esattamente "id", "ID", "Id"
detectedKey = columns.FirstOrDefault(c =>
c.Equals("id", StringComparison.OrdinalIgnoreCase) ||
c.Equals("ID", StringComparison.Ordinal) ||
c.Equals("Id", StringComparison.Ordinal));
// 2. Se non trovato, cerca colonne che terminano con "id", "ID", "Id"
if (detectedKey == null)
{
detectedKey = columns.FirstOrDefault(c =>
c.EndsWith("id", StringComparison.OrdinalIgnoreCase) ||
c.EndsWith("ID", StringComparison.Ordinal) ||
c.EndsWith("Id", StringComparison.Ordinal));
}
// 3. Se non trovato, cerca colonne che contengono pattern di chiave
if (detectedKey == null)
{
foreach (var pattern in keyPatterns)
{
detectedKey = columns.FirstOrDefault(c =>
c.Contains(pattern, StringComparison.OrdinalIgnoreCase));
if (detectedKey != null) break;
}
}
// 4. Auto-seleziona se trovato
if (!string.IsNullOrEmpty(detectedKey))
{
sourceKeyField = detectedKey;
suggestedPrimaryKey = detectedKey;
requiresManualKeySelection = false;
Logger.LogInformation("Chiave auto-selezionata per query custom: {KeyField}", detectedKey);
}
else
{
Logger.LogInformation("Nessuna chiave rilevabile automaticamente per query custom, selezione manuale richiesta");
}
}
catch (Exception ex)
{
Logger.LogError(ex, "Errore nell'auto-selezione della chiave per query custom");
sourceKeyField = "";
suggestedPrimaryKey = "";
requiresManualKeySelection = true;
}
}
/// <summary>
/// Valida una custom query SQL
/// </summary>
protected async Task ValidateCustomQuery()
{
if (string.IsNullOrWhiteSpace(customQuery))
{
isQueryValid = false;
queryValidationMessage = "Query vuota";
return;
}
isValidatingQuery = true;
IDatabaseManager? tempManager = null;
try
{
// Controllo di sicurezza: verifica che sia una SELECT
if (!IsSelectQuery(customQuery))
{
isQueryValid = false;
queryValidationMessage = "Solo query SELECT sono permesse per sicurezza";
return;
}
var cleanQuery = CleanQuery(customQuery);
// Trova la credenziale per determinare il tipo di database
var credential = databaseCredentials.FirstOrDefault(c => c.Name == selectedDatabaseCredential);
if (credential == null)
{
isQueryValid = false;
queryValidationMessage = "Credenziale database non trovata";
return;
}
// Per ODBC e OLE DB, crea un database manager temporaneo se non esiste
var managerToUse = currentDatabaseManager;
if (managerToUse == null && (IsOdbcConnection() || IsOleDbConnection()))
{
Logger.LogInformation("Creando database manager temporaneo per validazione query {Type}",
IsOdbcConnection() ? "ODBC" : "OLE DB");
tempManager = await ConnectionFactory.CreateDatabaseManagerAsync(selectedDatabaseCredential);
managerToUse = tempManager;
}
// Se ancora non abbiamo un manager, errore
if (managerToUse == null)
{
isQueryValid = false;
queryValidationMessage = "Manager database non disponibile. Connettersi prima di validare la query.";
return;
}
// Crea una query di test con sintassi appropriata per il tipo di database
var testQuery = CreateLimitedQuery(cleanQuery, credential.DatabaseType, 1);
Logger.LogInformation("Validando query: {Query}", testQuery);
// Prova a eseguire la query per validarla
var testResults = await managerToUse.ExecuteRawQueryAsync(testQuery);
if (testResults != null && testResults.Any())
{
var firstRow = testResults.First();
queryColumns = firstRow.Keys.ToList();
isQueryValid = true;
queryValidationMessage = $"Query valida - {queryColumns.Count} colonne rilevate";
// Clear mappings quando cambia la query
ClearAllMappings();
// AUTO-SELECT della chiave per query custom
TryAutoSelectKeyForQuery(queryColumns);
Logger.LogInformation("Query validata con successo: {ColumnCount} colonne", queryColumns.Count);
// Per ODBC e OLE DB, salva il manager temporaneo per riuso
if ((IsOdbcConnection() || IsOleDbConnection()) && currentDatabaseManager == null && tempManager != null)
{
currentDatabaseManager = tempManager;
tempManager = null; // Non distruggerlo nel finally
}
}
else
{
isQueryValid = false;
queryValidationMessage = "La query non ha restituito risultati o colonne";
}
}
catch (Exception ex)
{
Logger.LogError(ex, "Errore nella validazione della query");
isQueryValid = false;
queryValidationMessage = $"Errore nella validazione: {ex.Message}";
}
finally
{
isValidatingQuery = false;
// Pulisci il manager temporaneo se non è stato salvato
if (tempManager != null)
{
try { tempManager.Dispose(); } catch { /* Ignora errori di dispose */ }
}
StateHasChanged();
}
}
/// <summary>
/// Carica un'anteprima dei dati della query
/// </summary>
protected async Task LoadQueryPreview()
{
if (!isQueryValid || string.IsNullOrWhiteSpace(customQuery) || currentDatabaseManager == null)
return;
isLoadingPreview = true;
try
{
var cleanQuery = CleanQuery(customQuery);
// Trova la credenziale per determinare il tipo di database
var credential = databaseCredentials.FirstOrDefault(c => c.Name == selectedDatabaseCredential);
if (credential == null)
{
queryValidationMessage = "Credenziale database non trovata";
return;
}
// Crea una query di anteprima con sintassi appropriata per il tipo di database
var previewQuery = CreateLimitedQuery(cleanQuery, credential.DatabaseType, 10);
Logger.LogInformation("Caricando anteprima con query: {Query}", previewQuery);
var previewResults = await currentDatabaseManager.ExecuteRawQueryAsync(previewQuery);
queryPreviewData = previewResults.ToList();
showQueryPreview = true;
Logger.LogInformation("Caricata anteprima query con {RecordCount} record", queryPreviewData.Count);
}
catch (Exception ex)
{
Logger.LogError(ex, "Errore nel caricamento anteprima query");
queryValidationMessage = $"Errore anteprima: {ex.Message}";
}
finally
{
isLoadingPreview = false;
StateHasChanged();
}
}
/// <summary>
/// Crea una query limitata in base al tipo di database
/// </summary>
protected string CreateLimitedQuery(string baseQuery, DatabaseType databaseType, int limit)
{
return databaseType switch
{
DatabaseType.SqlServer => $"SELECT TOP {limit} * FROM ({baseQuery}) AS subquery",
DatabaseType.OleDb => $"SELECT TOP {limit} * FROM ({baseQuery}) AS subquery",
DatabaseType.Oracle => $"SELECT * FROM ({baseQuery}) WHERE ROWNUM <= {limit}",
DatabaseType.MySql => $"{baseQuery} LIMIT {limit}",
DatabaseType.PostgreSql => $"{baseQuery} LIMIT {limit}",
DatabaseType.Sqlite => $"{baseQuery} LIMIT {limit}",
DatabaseType.DB2 => $"SELECT * FROM ({baseQuery}) FETCH FIRST {limit} ROWS ONLY",
DatabaseType.SapHana => $"{baseQuery} LIMIT {limit}",
_ => $"{baseQuery} LIMIT {limit}" // Default a LIMIT per database non riconosciuti
};
}
/// <summary>
/// Nasconde l'anteprima della query
/// </summary>
protected void HideQueryPreview()
{
showQueryPreview = false;
StateHasChanged();
}
/// <summary>
/// Tenta di caricare gli schemi con query diretta
/// </summary>
protected async Task TryLoadSchemasWithDirectQuery()
{
if (currentDatabaseManager == null)
return;
try
{
// Query diverse per ogni tipo di database - focalizzate sui database/cataloghi
var credential = databaseCredentials.FirstOrDefault(c => c.Name == selectedDatabaseCredential);
if (credential == null) return;
string? schemaQuery = credential.DatabaseType switch
{
DatabaseType.SqlServer => "SELECT name FROM sys.databases WHERE name NOT IN ('master', 'tempdb', 'model', 'msdb') AND state = 0",
DatabaseType.PostgreSql => "SELECT datname FROM pg_database WHERE datistemplate = false AND datname NOT IN ('postgres', 'template0', 'template1')",
DatabaseType.MySql => "SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME NOT IN ('information_schema', 'performance_schema', 'mysql', 'sys')",
DatabaseType.Oracle => "SELECT DISTINCT OWNER FROM ALL_TABLES WHERE OWNER NOT IN ('SYS', 'SYSTEM', 'DBSNMP', 'SYSMAN', 'OUTLN', 'ANONYMOUS', 'CTXSYS', 'EXFSYS', 'LBACSYS', 'MDSYS', 'MGMT_VIEW', 'OLAPSYS', 'OWBSYS', 'ORDDATA', 'ORDSYS', 'SI_INFORMTN_SCHEMA', 'WK_TEST', 'WKPROXY', 'WMSYS', 'XDB', 'APEX_040000', 'APEX_PUBLIC_USER', 'DIP', 'FLOWS_FILES', 'HR', 'IX', 'OE', 'PM', 'SCOTT', 'SH', 'BI')",
_ => null
};
if (!string.IsNullOrEmpty(schemaQuery))
{
Logger.LogInformation("Eseguendo query per database/schemi: {Query}", schemaQuery);
var results = await currentDatabaseManager.ExecuteRawQueryAsync(schemaQuery);
if (results != null && results.Any())
{
var schemas = results.Select(row =>
{
var firstValue = row.Values.FirstOrDefault();
return firstValue?.ToString() ?? "";
})
.Where(schema => !string.IsNullOrEmpty(schema))
.OrderBy(schema => schema)
.ToList();
if (schemas.Any())
{
availableSchemas.AddRange(schemas);
Logger.LogInformation("Caricati {SchemaCount} database/schemi via query diretta per {DatabaseType}: {Schemas}",
schemas.Count, credential.DatabaseType, string.Join(", ", schemas));
}
}
}
}
catch (Exception ex)
{
Logger.LogWarning(ex, "Errore nel caricamento database/schemi via query diretta");
}
}
/// <summary>
/// Gestisce la selezione database quando la discovery restituisce dizionario vuoto
/// </summary>
protected async Task HandleDatabaseSelectionRequired()
{
isLoadingDatabases = true;
showDatabaseSelectionModal = true;
availableDatabases.Clear();
selectedDatabase = "";
try
{
if (currentDatabaseManager != null)
{
var dbs = await currentDatabaseManager.GetAvailableDatabasesAsync();
availableDatabases = dbs ?? new List<string>();
}
}
catch (Exception ex)
{
databaseErrorMessage = $"Errore nel caricamento dei database: {ex.Message}";
}
finally
{
isLoadingDatabases = false;
}
}
/// <summary>
/// Gestisce la selezione di un database specifico
/// </summary>
protected async Task OnDatabaseSelected()
{
try
{
if (string.IsNullOrEmpty(selectedDatabase))
{
databaseErrorMessage = "Nessun database selezionato";
return;
}
showDatabaseSelectionModal = false;
Logger.LogInformation("Database selezionato: {DatabaseName}. Riconnessione in corso...", selectedDatabase);
// Riconnessione al database selezionato
await ConnectToDatabaseWithSpecificDatabase(selectedDatabase);
}
catch (Exception ex)
{
Logger.LogError(ex, "Errore nella selezione del database: {DatabaseName}", selectedDatabase);
databaseErrorMessage = $"Errore nella connessione al database {selectedDatabase}: {ex.Message}";
}
}
private IEnumerable<string> FilterSystemDatabases(List<string> allDatabases)
{
// Trova la credenziale per determinare il tipo di database
var credential = databaseCredentials.FirstOrDefault(c => c.Name == selectedDatabaseCredential);
if (credential == null)
{
Logger.LogWarning("Credenziale non trovata per filtraggio database di sistema");
return allDatabases; // Restituisce tutti se non riesce a determinare il tipo
}
var databaseType = credential.DatabaseType;
// Filtri per SQL Server
if (databaseType == DatabaseType.SqlServer)
{
var sqlServerSystemDatabases = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
"master", "tempdb", "model", "msdb", "Resource", "mssqlsystemresource",
"ReportServer", "ReportServerTempDB", "SSISDB", "distribution"
};
return allDatabases.Where(db => !sqlServerSystemDatabases.Contains(db));
}
// TODO: Implementare filtri per altri tipi di database
if (databaseType == DatabaseType.MySql)
{
Logger.LogInformation("Filtro database di sistema MySQL - DA IMPLEMENTARE");
return allDatabases; // Per ora restituisce tutti
}
if (databaseType == DatabaseType.PostgreSql)
{
Logger.LogInformation("Filtro database di sistema PostgreSQL - DA IMPLEMENTARE");
return allDatabases; // Per ora restituisce tutti
}
if (databaseType == DatabaseType.Oracle)
{
Logger.LogInformation("Filtro database di sistema Oracle - DA IMPLEMENTARE");
return allDatabases; // Per ora restituisce tutti
}
Logger.LogWarning("Tipo database non riconosciuto per filtraggio: {DatabaseType}", databaseType);
return allDatabases; // Restituisce tutti per tipi non riconosciuti
}
private void CancelDatabaseSelection()
{
showDatabaseSelectionModal = false;
selectedDatabase = "";
databaseErrorMessage = "Selezione database annullata";
Logger.LogInformation("Selezione database annullata dall'utente");
}
// ===== FINE METODI DATABASE =====
}