e70abcdcb1
Aggiunge un connettore completamente managed per database Visual FoxPro e dBase, utilizzabile come sorgente dati in tutti i flussi dell'applicazione (discovery manuale, esecuzione schedulata, hash/change-detection, backup/restore). ## Motivazione Il provider OLE DB ufficiale (VFPOLEDB.1) è solo a 32-bit, deprecato e richiede installazione separata. Il connettore managed usa DbfDataReader 1.0.0 (MIT), legge direttamente i file .dbf/.fpt/.dbc a 64-bit senza dipendenze esterne e con streaming efficiente (tabelle da centinaia di MB con uso di memoria contenuto). ## Nuovi file - DataConnection/DB/FoxPro/FoxProConnectionInfo.cs Classe sealed con le informazioni di connessione risolte (cartella, percorso .dbc, encoding, flag IncludeDeleted). Proprietà di convenienza IsContainer e DisplayName. - DataConnection/DB/FoxPro/FoxProReader.cs Helper statico che registra CodePagesEncodingProvider, risolve la connection string (path diretto, stile OLE DB con Provider=vfpoledb, chiave=valore), verifica l'accesso al filesystem, legge il catalogo .dbc (il file .dbc è esso stesso un DBF; filtro su OBJECTTYPE='Table' + cross-check esistenza fisica .dbf), enumera le tabelle free, mappa i tipi VFP (Character, Memo, Numeric, Float, Double, Integer, Currency, Date, DateTime, Logical, General) in descrizioni leggibili, esegue streaming dei record via DbfDataReader, auto-rileva l'encoding dall'header DBF (language driver byte) con fallback a code page 1252, e analizza query SELECT [TOP n] * FROM tabella. - DataConnection/DB/FoxProDatabaseManager.cs Implementa IDatabaseManager. Lettura: TestConnectionAsync, GetTableNamesAsync, GetTableSchemaAsync, GetDatabaseSchemaAsync, GetAllRecordsAsync, ExecuteRawQueryAsync, GetPrimaryKeyFieldAsync (ritorna null, selezione manuale chiave), GetAvailableDatabasesAsync, ChangeDatabaseAsync (no-op per sorgenti file-based). Scrittura: tutti i metodi di modifica (Insert/Update/Delete/Upsert/BulkInsert/ ExecuteCommand/ExecuteNonQuery) lanciano NotSupportedException con messaggio esplicito. - DataConnection/DB/EF/SchemaProviders/FoxProSchemaProvider.cs Implementa IDatabaseSchemaProvider delegando a FoxProReader. Gestione errori per tabella in GetDatabaseSchemaAsync (una tabella non apribile non blocca la discovery). - FOXPRO_CONNECTION.md Guida utente: passo-passo per creare la credenziale, formato connection string, tabella tipi VFP supportati, note su sola lettura e limitazioni query, tabella risoluzione problemi (caratteri accentati, tabelle mancanti, ecc.). ## File modificati - DataConnection/DataConnection.csproj Aggiunto DbfDataReader 1.0.0 (porta transitivamente System.Text.Encoding.CodePages >= 10.0.3; rimossa dipendenza esplicita alla versione 9.0.3 che causava NU1605). - DataConnection/DB/Enums/DatabaseType.cs Aggiunto valore Foxpro in fondo all'enum (preserva valori già persistiti). - CredentialManager/Models/CredentialModels.cs Aggiunto DatabaseType.Foxpro all'enum e metodo BuildFoxproConnectionString che genera "Data Source=percorso[;CodePage=n][;IncludeDeleted=true]". - DataConnection/CredentialManagement/Models/CredentialExtensions.cs Mappatura bidirezionale Foxpro tra enum CredentialManager e DataConnection. - DataConnection/CredentialManagement/Services/DataConnectionCredentialService.cs Aggiunto TestFoxproConnection: verifica percorso accessibile, legge nomi tabelle, restituisce messaggio con conteggio tabelle, encoding rilevato e nota sola lettura. - DataConnection/DB/EF/DatabaseSchemaProviderFactory.cs Caso Foxpro => new FoxProSchemaProvider(). - Data_Coupler/Services/DataConnectionFactory.cs Branch Foxpro => new FoxProDatabaseManager(connectionString). - Data_Coupler/Extensions/DataCoupler/DatabaseMethod.cs Aggiunto IsFoxproConnection() e caso Foxpro in CreateLimitedQuery (SELECT TOP n * FROM (query) AS subquery). - CredentialManager/Integration/DataConnectionHelper.cs ValidateDatabaseCredential: introdotto flag isFileBased (Sqlite || Foxpro) per esonerare host/utente/password/porta; messaggio di errore specifico per il campo percorso FoxPro. - Data_Coupler/Pages/CredentialManagement.razor Aggiunta opzione "Visual FoxPro (.dbc / .dbf)" nel selettore tipo database. Sezione di configurazione dedicata: banner sola lettura, campo percorso con esempi (.dbc e cartella), dropdown code page (Auto/1252/1250/1251/850/437/65001), checkbox record cancellati, pannello tipi supportati, anteprima connection string. Fix validazione form test-connessione: branch Foxpro che verifica solo DatabaseName (non Host/Username/Password), eliminando il falso errore "Il campo Host è obbligatorio". Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
961 lines
36 KiB
C#
961 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>
|
|
/// Verifica se la credenziale database selezionata è di tipo Visual FoxPro
|
|
/// (sorgente file-based in sola lettura).
|
|
/// </summary>
|
|
protected bool IsFoxproConnection()
|
|
{
|
|
if (string.IsNullOrEmpty(selectedDatabaseCredential))
|
|
return false;
|
|
|
|
var credential = databaseCredentials.FirstOrDefault(c => c.Name == selectedDatabaseCredential);
|
|
return credential?.DatabaseType == DatabaseType.Foxpro;
|
|
}
|
|
|
|
/// <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.Foxpro => $"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 =====
|
|
}
|