diff --git a/DataConnection/DB/EF/EFCoreDatabaseManager.cs b/DataConnection/DB/EF/EFCoreDatabaseManager.cs index de40674..3281304 100644 --- a/DataConnection/DB/EF/EFCoreDatabaseManager.cs +++ b/DataConnection/DB/EF/EFCoreDatabaseManager.cs @@ -142,7 +142,8 @@ public class EFCoreDatabaseManager : IDatabaseManager { return await _context.Database.ExecuteSqlRawAsync(sql, parameters); } - public async Task>> GetDatabaseSchemaAsync() + + public async Task>> GetDatabaseSchemaAsync() { try { @@ -166,7 +167,91 @@ public class EFCoreDatabaseManager : IDatabaseManager Console.WriteLine($"Errore nel recupero dello schema del database: {ex.Message}"); throw; } - } public async Task>> GetAllRecordsAsync(string tableName) + } + + /// + /// Ottiene la lista dei database disponibili sul server + /// + /// Lista dei nomi dei database disponibili + public async Task> GetAvailableDatabasesAsync() + { + try + { + // Usa la factory per ottenere il provider appropriato in base al tipo di database + var schemaProvider = DatabaseSchemaProviderFactory.CreateProvider(_options.DatabaseType); + + // Usa il provider per ottenere la lista dei database + var connectionString = _context.Database.GetConnectionString(); + if (connectionString == null) + throw new InvalidOperationException("Connection string is null"); + + var result = await schemaProvider.GetAvailableDatabasesAsync(connectionString); + + return result.ToList(); + } + catch (Exception ex) + { + Console.WriteLine($"Errore nel recupero della lista dei database: {ex.Message}"); + throw; + } + } + + /// + /// Ottiene solo la lista dei nomi delle tabelle disponibili (senza dettagli delle colonne) + /// + /// Lista dei nomi delle tabelle + public async Task> GetTableNamesAsync() + { + try + { + // Usa la factory per ottenere il provider appropriato in base al tipo di database + var schemaProvider = DatabaseSchemaProviderFactory.CreateProvider(_options.DatabaseType); + + // Usa il provider per ottenere la lista delle tabelle + var connectionString = _context.Database.GetConnectionString(); + if (connectionString == null) + throw new InvalidOperationException("Connection string is null"); + + var result = await schemaProvider.GetTableNamesAsync(connectionString); + + return result; + } + catch (Exception ex) + { + Console.WriteLine($"Errore nel recupero della lista delle tabelle: {ex.Message}"); + throw; + } + } + + /// + /// Ottiene i dettagli delle colonne per una specifica tabella + /// + /// Nome della tabella (con schema se necessario) + /// Lista delle informazioni sulle colonne + public async Task> GetTableSchemaAsync(string tableName) + { + try + { + // Usa la factory per ottenere il provider appropriato in base al tipo di database + var schemaProvider = DatabaseSchemaProviderFactory.CreateProvider(_options.DatabaseType); + + // Usa il provider per ottenere lo schema della tabella + var connectionString = _context.Database.GetConnectionString(); + if (connectionString == null) + throw new InvalidOperationException("Connection string is null"); + + var result = await schemaProvider.GetTableSchemaAsync(connectionString, tableName); + + return result; + } + catch (Exception ex) + { + Console.WriteLine($"Errore nel recupero dello schema della tabella {tableName}: {ex.Message}"); + throw; + } + } + + public async Task>> GetAllRecordsAsync(string tableName) { try { @@ -226,47 +311,6 @@ public class EFCoreDatabaseManager : IDatabaseManager } } - public async Task> GetAvailableDatabasesAsync() - { - try - { - var connectionString = _context.Database.GetConnectionString(); - if (connectionString == null) - throw new InvalidOperationException("Connection string is null"); - - // Crea una connessione al server (senza specificare il database) - var serverConnectionString = GetServerConnectionString(connectionString); - - using var connection = CreateConnection(serverConnectionString); - await connection.OpenAsync(); - - using var command = connection.CreateCommand(); - - // Query per ottenere i database disponibili (esclude quelli di sistema) - command.CommandText = @" - SELECT name - FROM sys.databases - WHERE state_desc = 'ONLINE' - AND name NOT IN ('master', 'tempdb', 'model', 'msdb', 'distribution') - ORDER BY name"; - - var databases = new List(); - using var reader = await command.ExecuteReaderAsync(); - - while (await reader.ReadAsync()) - { - databases.Add(reader.GetString(0)); - } - - return databases; - } - catch (Exception ex) - { - Console.WriteLine($"Errore nell'ottenere la lista dei database: {ex.Message}"); - throw; - } - } - public async Task ChangeDatabaseAsync(string databaseName) { try diff --git a/DataConnection/DB/EF/SchemaProviders/SqlServerSchemaProvider.cs b/DataConnection/DB/EF/SchemaProviders/SqlServerSchemaProvider.cs index 15e6edb..a75597d 100644 --- a/DataConnection/DB/EF/SchemaProviders/SqlServerSchemaProvider.cs +++ b/DataConnection/DB/EF/SchemaProviders/SqlServerSchemaProvider.cs @@ -21,13 +21,29 @@ public class SqlServerSchemaProvider : IDatabaseSchemaProvider { await connection.OpenAsync(); - // Prima verifichiamo se ci sono tabelle utente con una query semplice + // Verifica se la connection string specifica già un database + var connectionBuilder = new SqlConnectionStringBuilder(connectionString); + var currentDatabase = connectionBuilder.InitialCatalog; + + if (string.IsNullOrEmpty(currentDatabase)) + { + // Nessun database specificato - restituisce dizionario vuoto per indicare + // che è necessaria la selezione del database + Console.WriteLine("Nessun database specificato nella connection string. È richiesta la selezione del database."); + return new Dictionary>(); + } + + Console.WriteLine($"Analizzando database: {currentDatabase}"); + + // Prima verifichiamo se ci sono tabelle utente nel database specificato string testSql = "SELECT COUNT(*) FROM sys.tables WHERE is_ms_shipped = 0"; using (var testCommand = new SqlCommand(testSql, connection)) { var scalarResult = await testCommand.ExecuteScalarAsync(); var tableCount = scalarResult != null ? (int)scalarResult : 0; + Console.WriteLine($"Trovate {tableCount} tabelle utente nel database {currentDatabase}"); + if (tableCount == 0) { return new Dictionary>(); // Restituisce dizionario vuoto @@ -141,6 +157,236 @@ public class SqlServerSchemaProvider : IDatabaseSchemaProvider return result; } + /// + /// Ottiene la lista dei database disponibili sul server SQL Server + /// + public async Task> GetAvailableDatabasesAsync(string connectionString) + { + var databases = new List(); + + try + { + // Crea una connection string per il server (rimuove il database specifico) + var connectionBuilder = new SqlConnectionStringBuilder(connectionString); + connectionBuilder.InitialCatalog = ""; // Rimuove il database specifico + var serverConnectionString = connectionBuilder.ConnectionString; + + using (var connection = new SqlConnection(serverConnectionString)) + { + await connection.OpenAsync(); + + // Query per ottenere i database disponibili (esclude quelli di sistema) + string sql = @" + SELECT name + FROM sys.databases + WHERE state_desc = 'ONLINE' + AND name NOT IN ('master', 'tempdb', 'model', 'msdb', 'distribution') + AND HAS_DBACCESS(name) = 1 + ORDER BY name"; + + using (var command = new SqlCommand(sql, connection)) + { + using (var reader = await command.ExecuteReaderAsync()) + { + while (await reader.ReadAsync()) + { + databases.Add(reader.GetString(0)); + } + } + } + } + } + catch (Exception ex) + { + Console.WriteLine($"Errore nel recupero della lista dei database: {ex.Message}"); + throw; + } + + return databases; + } + + /// + /// Ottiene solo la lista dei nomi delle tabelle disponibili (senza dettagli delle colonne) + /// + public async Task> GetTableNamesAsync(string connectionString) + { + var tableNames = new List(); + + try + { + using (var connection = new SqlConnection(connectionString)) + { + await connection.OpenAsync(); + + // Verifica se la connection string specifica già un database + var connectionBuilder = new SqlConnectionStringBuilder(connectionString); + var currentDatabase = connectionBuilder.InitialCatalog; + + if (string.IsNullOrEmpty(currentDatabase)) + { + Console.WriteLine("Nessun database specificato nella connection string per ottenere i nomi delle tabelle."); + return new List(); + } + + Console.WriteLine($"Recuperando nomi tabelle dal database: {currentDatabase}"); + + // Query semplice per ottenere solo i nomi delle tabelle + string sql = @" + SELECT SCHEMA_NAME(t.schema_id) + '.' + t.name AS TableName + FROM sys.tables t + WHERE t.is_ms_shipped = 0 + ORDER BY TableName"; + + using (var command = new SqlCommand(sql, connection)) + { + using (var reader = await command.ExecuteReaderAsync()) + { + while (await reader.ReadAsync()) + { + tableNames.Add(reader.GetString(0)); + } + } + } + } + } + catch (Exception ex) + { + Console.WriteLine($"Errore nel recupero dei nomi delle tabelle: {ex.Message}"); + throw; + } + + return tableNames; + } + + /// + /// Ottiene i dettagli delle colonne per una specifica tabella + /// + public async Task> GetTableSchemaAsync(string connectionString, string tableName) + { + var columns = new List(); + + try + { + using (var connection = new SqlConnection(connectionString)) + { + await connection.OpenAsync(); + + // Verifica se la connection string specifica già un database + var connectionBuilder = new SqlConnectionStringBuilder(connectionString); + var currentDatabase = connectionBuilder.InitialCatalog; + + if (string.IsNullOrEmpty(currentDatabase)) + { + Console.WriteLine("Nessun database specificato nella connection string per ottenere lo schema della tabella."); + return new List(); + } + + Console.WriteLine($"Recuperando schema della tabella {tableName} dal database: {currentDatabase}"); + + // Separa schema e nome tabella + string schemaName = "dbo"; + string tableNameOnly = tableName; + if (tableName.Contains('.')) + { + var parts = tableName.Split('.'); + schemaName = parts[0]; + tableNameOnly = parts[1]; + } + + // Query per ottenere i dettagli delle colonne di una specifica tabella + string sql = @" + SELECT + c.name AS ColumnName, + tp.name AS DataType, + c.max_length, + c.precision, + c.scale, + c.is_nullable, + CASE WHEN pk.column_id IS NOT NULL THEN 1 ELSE 0 END AS IsPrimaryKey, + CASE WHEN fk.parent_column_id IS NOT NULL THEN 1 ELSE 0 END AS IsForeignKey, + SCHEMA_NAME(ref_t.schema_id) + '.' + ref_t.name AS ReferencedTable, + ref_c.name AS ReferencedColumn + FROM + sys.tables t + INNER JOIN + sys.columns c ON t.object_id = c.object_id + INNER JOIN + sys.types tp ON c.user_type_id = tp.user_type_id + LEFT JOIN + (SELECT + ic.object_id, + ic.column_id + FROM + sys.indexes i + INNER JOIN + sys.index_columns ic ON i.object_id = ic.object_id AND i.index_id = ic.index_id + WHERE + i.is_primary_key = 1) pk ON t.object_id = pk.object_id AND c.column_id = pk.column_id + LEFT JOIN + sys.foreign_key_columns fk ON t.object_id = fk.parent_object_id AND c.column_id = fk.parent_column_id + LEFT JOIN + sys.tables ref_t ON fk.referenced_object_id = ref_t.object_id + LEFT JOIN + sys.columns ref_c ON fk.referenced_object_id = ref_c.object_id AND fk.referenced_column_id = ref_c.column_id + WHERE + t.is_ms_shipped = 0 + AND SCHEMA_NAME(t.schema_id) = @schemaName + AND t.name = @tableName + ORDER BY + c.column_id"; + + using (var command = new SqlCommand(sql, connection)) + { + // Aggiungi parametri per evitare SQL injection + var schemaParam = command.CreateParameter(); + schemaParam.ParameterName = "@schemaName"; + schemaParam.Value = schemaName; + command.Parameters.Add(schemaParam); + + var tableParam = command.CreateParameter(); + tableParam.ParameterName = "@tableName"; + tableParam.Value = tableNameOnly; + command.Parameters.Add(tableParam); + + using (var reader = await command.ExecuteReaderAsync()) + { + while (await reader.ReadAsync()) + { + // Formato del tipo di dati con precisione e scala per tipi numerici o lunghezza per tipi stringa + string dataType = reader.GetString(1); + int maxLength = reader.GetInt16(2); + byte precision = reader.GetByte(3); + byte scale = reader.GetByte(4); + + // Formattazione tipo di dati per SQL Server + string formattedDataType = FormatSqlServerDataType(dataType, maxLength, precision, scale); + + var columnInfo = new DbColumnInfo + { + Name = reader.GetString(0), + DataType = formattedDataType, + IsNullable = reader.GetBoolean(5), + IsPrimaryKey = reader.GetInt32(6) == 1, + IsForeignKey = reader.GetInt32(7) == 1, + ReferencedTable = reader.IsDBNull(8) ? null : reader.GetString(8), + ReferencedColumn = reader.IsDBNull(9) ? null : reader.GetString(9) + }; + + columns.Add(columnInfo); + } + } + } + } + } + catch (Exception ex) + { + Console.WriteLine($"Errore nel recupero dello schema della tabella {tableName}: {ex.Message}"); + throw; + } + + return columns; + } + private static string FormatSqlServerDataType(string dataType, int maxLength, byte precision, byte scale) { string formattedDataType = dataType; diff --git a/DataConnection/DB/Interfaces/IDatabaseManager.cs b/DataConnection/DB/Interfaces/IDatabaseManager.cs index 61215e4..cb4b045 100644 --- a/DataConnection/DB/Interfaces/IDatabaseManager.cs +++ b/DataConnection/DB/Interfaces/IDatabaseManager.cs @@ -65,6 +65,17 @@ public interface IDatabaseManager : IDisposable /// Task>> GetDatabaseSchemaAsync(); + /// + /// Ottiene solo la lista dei nomi delle tabelle disponibili (senza dettagli delle colonne) + /// + Task> GetTableNamesAsync(); + + /// + /// Ottiene i dettagli delle colonne per una specifica tabella + /// + /// Nome della tabella (con schema se necessario) + Task> GetTableSchemaAsync(string tableName); + /// /// Ottiene tutti i record da una tabella specifica come dizionari chiave-valore /// diff --git a/DataConnection/DB/Interfaces/IDatabaseSchemaProvider.cs b/DataConnection/DB/Interfaces/IDatabaseSchemaProvider.cs index 6ef0826..186381b 100644 --- a/DataConnection/DB/Interfaces/IDatabaseSchemaProvider.cs +++ b/DataConnection/DB/Interfaces/IDatabaseSchemaProvider.cs @@ -14,4 +14,26 @@ public interface IDatabaseSchemaProvider /// Stringa di connessione al database /// Struttura gerarchica delle tabelle e delle loro colonne Task>> GetDatabaseSchemaAsync(string connectionString); + + /// + /// Ottiene la lista dei database disponibili sul server + /// + /// Stringa di connessione al server (senza specificare il database) + /// Lista dei nomi dei database disponibili + Task> GetAvailableDatabasesAsync(string connectionString); + + /// + /// Ottiene solo la lista dei nomi delle tabelle disponibili (senza dettagli delle colonne) + /// + /// Stringa di connessione al database + /// Lista dei nomi delle tabelle + Task> GetTableNamesAsync(string connectionString); + + /// + /// Ottiene i dettagli delle colonne per una specifica tabella + /// + /// Stringa di connessione al database + /// Nome della tabella (con schema se necessario) + /// Lista delle informazioni sulle colonne + Task> GetTableSchemaAsync(string connectionString, string tableName); } \ No newline at end of file diff --git a/Data_Coupler/Pages/DataCoupler.razor b/Data_Coupler/Pages/DataCoupler.razor index bf4b7cc..bee4946 100644 --- a/Data_Coupler/Pages/DataCoupler.razor +++ b/Data_Coupler/Pages/DataCoupler.razor @@ -234,11 +234,11 @@ } } - else if (databaseTables.Any()) + else if (availableTableNames.Any()) {
-
Tabelle Database (@databaseTables.Count disponibili):
+
Tabelle Database (@availableTableNames.Count disponibili):
@@ -1131,7 +1131,7 @@
- -@if (showDatabaseSelectionModal) -{ - -} - @if (showSchemaSelectionModal) { @@ -1267,3 +1211,60 @@
} + + +@if (showDatabaseSelectionModal) +{ + +} diff --git a/Data_Coupler/Pages/DataCoupler.razor.cs b/Data_Coupler/Pages/DataCoupler.razor.cs index a7884b7..0cc8ad4 100644 --- a/Data_Coupler/Pages/DataCoupler.razor.cs +++ b/Data_Coupler/Pages/DataCoupler.razor.cs @@ -57,19 +57,21 @@ public partial class DataCoupler : ComponentBase private string restErrorMessage = ""; // Database discovery - private Dictionary> databaseTables = new(); + private List availableTableNames = new(); // Solo nomi delle tabelle + private Dictionary> databaseTables = new(); // Schema dettagliato per tabelle caricate private string selectedTable = ""; private string databaseSearchTerm = ""; - // Database selection + // Database selection - per gestire la selezione del database quando non specificato nella connection string private List availableDatabases = new(); - private List availableSchemas = new(); private string selectedDatabase = ""; - private string selectedSchema = ""; - private bool showDatabaseSelection = false; private bool showDatabaseSelectionModal = false; - private bool showSchemaSelectionModal = false; private bool isLoadingDatabases = false; + + // Database selection (schemas only) + private List availableSchemas = new(); + private string selectedSchema = ""; + private bool showSchemaSelectionModal = false; private bool isLoadingSchemas = false; // Custom query functionality @@ -110,7 +112,6 @@ public partial class DataCoupler : ComponentBase private string sourceKeyField = ""; // Campo che identifica univocamente il record sorgente private string suggestedPrimaryKey = ""; // Campo PK suggerito per database private bool requiresManualKeySelection = false; // Flag per indicare se è richiesta selezione manuale - private Dictionary sourceKeyMappings = new(); // Per CSV: mapppatura colonna -> nome campo chiave private bool useRecordAssociations = true; // Se utilizzare il sistema di associazioni // Trasferimento dati @@ -745,6 +746,12 @@ public partial class DataCoupler : ComponentBase databaseSearchTerm = ""; databaseErrorMessage = ""; + // Reset database selection + availableDatabases.Clear(); + selectedDatabase = ""; + showDatabaseSelectionModal = false; + isLoadingDatabases = false; + // Reset custom query state useCustomQuery = false; customQuery = ""; @@ -774,7 +781,7 @@ public partial class DataCoupler : ComponentBase // Clear mappings when resetting REST state ClearAllMappings(); - }private async Task ConnectToDatabase() + } private async Task ConnectToDatabase() { if (string.IsNullOrEmpty(selectedDatabaseCredential)) return; @@ -783,7 +790,8 @@ public partial class DataCoupler : ComponentBase databaseErrorMessage = ""; try - { // Trova la credenziale + { + // Trova la credenziale var credential = databaseCredentials.FirstOrDefault(c => c.Name == selectedDatabaseCredential); if (credential == null) { @@ -797,42 +805,51 @@ public partial class DataCoupler : ComponentBase { databaseErrorMessage = $"Connessione fallita: {message}"; return; - } // Crea il database manager usando il factory con le credenziali complete + } + + // 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"); - Logger.LogInformation("Iniziando discovery dello schema per database {DatabaseType} con credenziale: {CredentialName}", credential.DatabaseType, selectedDatabaseCredential); - - // Discovery dello schema con try-catch specifico - try + // Verifica se il database è specificato nella connection string + bool isDatabaseSpecified = await IsDatabaseSpecifiedInConnectionString(credential); + + if (isDatabaseSpecified) { - var schema = await currentDatabaseManager.GetDatabaseSchemaAsync(); - - Logger.LogInformation("Schema discovery completato. Tipo restituito: {SchemaType}, Numero elementi: {Count}", - schema?.GetType().Name ?? "null", - schema?.Count() ?? 0); - - databaseTables = schema as Dictionary> ?? - (schema != null ? new Dictionary>(schema) : new Dictionary>()); - - Logger.LogInformation("Database tables dopo conversione: {Count} tabelle", databaseTables.Count); - - if (databaseTables.Count == 0) + Logger.LogInformation("Database specificato nella connection string. Procedendo con discovery tabelle."); + try { - // Se non ci sono tabelle, potrebbe essere perché non è stato selezionato un database specifico - HandleDatabaseSelectionRequired(); + 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; } } - catch (Exception schemaEx) + else { - Logger.LogError(schemaEx, "Errore specifico durante lo schema discovery"); - databaseErrorMessage = $"Errore nello schema discovery: {schemaEx.Message}"; - throw; + 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; + } } - - isDatabaseConnected = true; } catch (Exception ex) { @@ -842,8 +859,9 @@ public partial class DataCoupler : ComponentBase finally { isConnectingDatabase = false; + StateHasChanged(); } - } private async Task ConnectToRestApi() + }private async Task ConnectToRestApi() { if (string.IsNullOrEmpty(selectedRestCredential)) return; @@ -926,6 +944,21 @@ public partial class DataCoupler : ComponentBase 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) { @@ -987,9 +1020,9 @@ public partial class DataCoupler : ComponentBase private IEnumerable GetFilteredDatabaseTables() { if (string.IsNullOrEmpty(databaseSearchTerm)) - return databaseTables.Keys; + return availableTableNames; - return databaseTables.Keys.Where(table => + return availableTableNames.Where(table => table.Contains(databaseSearchTerm, StringComparison.OrdinalIgnoreCase)); } @@ -1864,7 +1897,8 @@ public partial class DataCoupler : ComponentBase if (databaseTables.Count == 0) { // Se non ci sono tabelle, potrebbe essere necessario selezionare un database specifico - HandleDatabaseSelectionRequired(); + // Schema discovery completato senza successo + databaseErrorMessage = "Impossibile rilevare le tabelle del database. Verificare le credenziali di connessione."; } else { @@ -1923,50 +1957,6 @@ public partial class DataCoupler : ComponentBase } } - /// - /// Gestisce la situazione quando è richiesta la selezione di un database o schema specifico - /// - private async void HandleDatabaseSelectionRequired() - { - try - { - Logger.LogInformation("Schema discovery non ha restituito risultati. Tentativo di scoprire database disponibili..."); - - // Prima prova a ottenere la lista dei database disponibili - await LoadAvailableDatabases(); - - if (availableDatabases.Any()) - { - // Se abbiamo database disponibili, mostra il modal per la selezione del database - Logger.LogInformation("Trovati {DatabaseCount} database disponibili", availableDatabases.Count); - showDatabaseSelectionModal = true; - StateHasChanged(); - } - else - { - // Se non ci sono database, prova con gli schemi - await LoadAvailableSchemas(); - - if (availableSchemas.Any()) - { - Logger.LogInformation("Trovati {SchemaCount} schemi disponibili", availableSchemas.Count); - showSchemaSelectionModal = true; - StateHasChanged(); - } - else - { - // Nessuna opzione disponibile - databaseErrorMessage = "Impossibile rilevare database o schemi. Verificare le credenziali di connessione o il tipo di database."; - } - } - } - catch (Exception ex) - { - Logger.LogError(ex, "Errore nella gestione della selezione database/schema"); - databaseErrorMessage = $"Errore nella rilevazione di database/schemi: {ex.Message}"; - } - } - /// /// Estrae lo schema dal nome completo di una tabella /// @@ -2195,7 +2185,7 @@ public partial class DataCoupler : ComponentBase { return databaseType switch { - DatabaseType.SqlServer => $"SELECT TOP {limit} * FROM ({baseQuery}) AS subquery", + DatabaseType.SqlServer => $"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}", @@ -2235,16 +2225,6 @@ public partial class DataCoupler : ComponentBase return null; } - /// - /// Annulla la selezione del database - /// - private void CancelDatabaseSelection() - { - showDatabaseSelectionModal = false; - selectedDatabase = ""; - StateHasChanged(); - } - /// /// Annulla la selezione dello schema /// @@ -2255,106 +2235,6 @@ public partial class DataCoupler : ComponentBase StateHasChanged(); } - /// - /// Conferma la selezione del database - /// - private async Task OnDatabaseSelected() - { - if (string.IsNullOrEmpty(selectedDatabase)) - return; - - showDatabaseSelectionModal = false; - - try - { - Logger.LogInformation("Database selezionato: {Database}. Tentativo di connessione diretta...", selectedDatabase); - - // Trova la credenziale corrente - var credential = databaseCredentials.FirstOrDefault(c => c.Name == selectedDatabaseCredential); - if (credential == null) - { - databaseErrorMessage = "Credenziale database non trovata"; - return; - } - - isConnectingDatabase = true; - databaseErrorMessage = ""; - - // Disponi il manager precedente - currentDatabaseManager?.Dispose(); - currentDatabaseManager = null; - - // Per ora, proviamo a usare il database manager esistente e cercare le tabelle con query dirette - // TODO: In futuro, modificare il ConnectionFactory per supportare database specifici - currentDatabaseManager = await ConnectionFactory.CreateDatabaseManagerAsync(selectedDatabaseCredential); - - // Prova a ottenere le tabelle del database specifico usando query dirette - var tablesQuery = credential.DatabaseType switch - { - DatabaseType.SqlServer => $"SELECT TABLE_NAME FROM {selectedDatabase}.INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE'", - DatabaseType.MySql => $"SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = '{selectedDatabase}' AND TABLE_TYPE = 'BASE TABLE'", - DatabaseType.PostgreSql => $"SELECT tablename as TABLE_NAME FROM pg_tables WHERE schemaname = '{selectedDatabase}'", - DatabaseType.Oracle => $"SELECT TABLE_NAME FROM ALL_TABLES WHERE OWNER = '{selectedDatabase.ToUpper()}'", - _ => $"SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = '{selectedDatabase}' AND TABLE_TYPE = 'BASE TABLE'" - }; - - Logger.LogInformation("Eseguendo query per tabelle del database {Database}: {Query}", selectedDatabase, tablesQuery); - - var tableResults = await currentDatabaseManager.ExecuteRawQueryAsync(tablesQuery); - - if (tableResults != null && tableResults.Any()) - { - // Converte i risultati in un dizionario di tabelle - databaseTables.Clear(); - - foreach (var row in tableResults) - { - var tableName = row.Values.FirstOrDefault()?.ToString(); - if (!string.IsNullOrEmpty(tableName)) - { - // Per ogni tabella, prova a ottenere le colonne - try - { - var fullTableName = credential.DatabaseType == DatabaseType.SqlServer - ? $"{selectedDatabase}.dbo.{tableName}" - : $"{selectedDatabase}.{tableName}"; - - var columns = await GetTableColumns(fullTableName, selectedDatabase, tableName); - databaseTables[fullTableName] = columns; - } - catch (Exception colEx) - { - Logger.LogWarning(colEx, "Impossibile ottenere colonne per la tabella {TableName}", tableName); - // Aggiungi la tabella anche senza colonne specifiche - databaseTables[tableName] = new List(); - } - } - } - - isDatabaseConnected = true; - Logger.LogInformation("Connessione completata con successo. Database: {Database}, Tabelle: {TableCount}", - selectedDatabase, databaseTables.Count); - databaseErrorMessage = ""; - } - else - { - databaseErrorMessage = $"Nessuna tabella trovata nel database {selectedDatabase}"; - Logger.LogWarning("Nessuna tabella trovata nel database {Database}", selectedDatabase); - } - } - catch (Exception ex) - { - Logger.LogError(ex, "Errore nella connessione con il database selezionato: {Database}", selectedDatabase); - databaseErrorMessage = $"Errore nella connessione con database {selectedDatabase}: {ex.Message}"; - isDatabaseConnected = false; - } - finally - { - isConnectingDatabase = false; - StateHasChanged(); - } - } - /// /// Ottiene le colonne di una tabella specifica /// @@ -2583,62 +2463,249 @@ public partial class DataCoupler : ComponentBase } } - /// - /// Carica la lista dei database disponibili - /// - private async Task LoadAvailableDatabases() + // Gestione selezione database quando la discovery restituisce dizionario vuoto + private async Task HandleDatabaseSelectionRequired() { - if (currentDatabaseManager == null) - return; - isLoadingDatabases = true; + showDatabaseSelectionModal = true; availableDatabases.Clear(); - + selectedDatabase = ""; try { - var credential = databaseCredentials.FirstOrDefault(c => c.Name == selectedDatabaseCredential); - if (credential == null) return; - - string? databaseQuery = credential.DatabaseType switch + if (currentDatabaseManager != null) { - DatabaseType.SqlServer => "SELECT name FROM sys.databases WHERE name NOT IN ('master', 'tempdb', 'model', 'msdb')", - 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')", - _ => null - }; - - if (!string.IsNullOrEmpty(databaseQuery)) - { - var results = await currentDatabaseManager.ExecuteRawQueryAsync(databaseQuery); - - if (results != null && results.Any()) - { - var databases = results.Select(row => - { - var firstValue = row.Values.FirstOrDefault(); - return firstValue?.ToString() ?? ""; - }) - .Where(db => !string.IsNullOrEmpty(db)) - .OrderBy(db => db) - .ToList(); - - if (databases.Any()) - { - availableDatabases.AddRange(databases); - Logger.LogInformation("Caricati {DatabaseCount} database per {DatabaseType}: {Databases}", - databases.Count, credential.DatabaseType, string.Join(", ", databases)); - } - } + var dbs = await currentDatabaseManager.GetAvailableDatabasesAsync(); + availableDatabases = dbs ?? new List(); } } catch (Exception ex) { - Logger.LogError(ex, "Errore nel caricamento dei database disponibili"); + databaseErrorMessage = $"Errore nel caricamento dei database: {ex.Message}"; } finally { isLoadingDatabases = false; } } + + private 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 void CancelDatabaseSelection() + { + showDatabaseSelectionModal = false; + selectedDatabase = ""; + databaseErrorMessage = "Selezione database annullata"; + Logger.LogInformation("Selezione database annullata dall'utente"); + } + + // Metodi helper per la connessione database + private Task 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 + } + } + + private 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; + } + } + + private 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}"; + throw; + } + finally + { + isLoadingDatabases = false; + } + } + + private IEnumerable FilterSystemDatabases(List 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(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 + } + + // Metodo per gestire la selezione di un database dal modale + + + private async Task ConnectToDatabaseWithSpecificDatabase(string databaseName) + { + try + { + 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; + } + } + + } diff --git a/Data_Coupler/wwwroot/data/credentials.db b/Data_Coupler/wwwroot/data/credentials.db index cbf213e..aae6556 100644 Binary files a/Data_Coupler/wwwroot/data/credentials.db and b/Data_Coupler/wwwroot/data/credentials.db differ