feat: Corregge la logica di rilevamento database specificato nella connection string

- Modifica IsDatabaseSpecifiedInConnectionString per verificare prima il campo DatabaseName della credenziale
- Aggiunge logging dettagliato per debugging del processo di connessione database
- Corregge il flusso di connessione per evitare il modale quando il database è già specificato
- Migliora la gestione degli errori nel caricamento tabelle dal database specificato
- Rimuove codice non raggiungibile nella logica di connessione database

Il bug precedente mostrava sempre il modale di selezione database anche quando
il database era specificato nel campo DatabaseName della credenziale, ora la
verifica segue la logica corretta:
1. Controlla se DatabaseName è valorizzato nella credenziale
2. Solo se vuoto, verifica i parametri Database=/Initial Catalog= nella connection string
This commit is contained in:
Alessio Dal Santo
2025-07-02 15:32:11 +02:00
parent 61883c3467
commit 77efe986a0
7 changed files with 728 additions and 337 deletions
+87 -43
View File
@@ -142,7 +142,8 @@ public class EFCoreDatabaseManager : IDatabaseManager
{ {
return await _context.Database.ExecuteSqlRawAsync(sql, parameters); return await _context.Database.ExecuteSqlRawAsync(sql, parameters);
} }
public async Task<IDictionary<string, IEnumerable<DbColumnInfo>>> GetDatabaseSchemaAsync()
public async Task<IDictionary<string, IEnumerable<DbColumnInfo>>> GetDatabaseSchemaAsync()
{ {
try try
{ {
@@ -166,7 +167,91 @@ public class EFCoreDatabaseManager : IDatabaseManager
Console.WriteLine($"Errore nel recupero dello schema del database: {ex.Message}"); Console.WriteLine($"Errore nel recupero dello schema del database: {ex.Message}");
throw; throw;
} }
} public async Task<IEnumerable<Dictionary<string, object>>> GetAllRecordsAsync(string tableName) }
/// <summary>
/// Ottiene la lista dei database disponibili sul server
/// </summary>
/// <returns>Lista dei nomi dei database disponibili</returns>
public async Task<List<string>> 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;
}
}
/// <summary>
/// Ottiene solo la lista dei nomi delle tabelle disponibili (senza dettagli delle colonne)
/// </summary>
/// <returns>Lista dei nomi delle tabelle</returns>
public async Task<IEnumerable<string>> 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;
}
}
/// <summary>
/// Ottiene i dettagli delle colonne per una specifica tabella
/// </summary>
/// <param name="tableName">Nome della tabella (con schema se necessario)</param>
/// <returns>Lista delle informazioni sulle colonne</returns>
public async Task<IEnumerable<DbColumnInfo>> 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<IEnumerable<Dictionary<string, object>>> GetAllRecordsAsync(string tableName)
{ {
try try
{ {
@@ -226,47 +311,6 @@ public class EFCoreDatabaseManager : IDatabaseManager
} }
} }
public async Task<List<string>> 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<string>();
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) public async Task ChangeDatabaseAsync(string databaseName)
{ {
try try
@@ -21,13 +21,29 @@ public class SqlServerSchemaProvider : IDatabaseSchemaProvider
{ {
await connection.OpenAsync(); 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<string, IEnumerable<DbColumnInfo>>();
}
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"; string testSql = "SELECT COUNT(*) FROM sys.tables WHERE is_ms_shipped = 0";
using (var testCommand = new SqlCommand(testSql, connection)) using (var testCommand = new SqlCommand(testSql, connection))
{ {
var scalarResult = await testCommand.ExecuteScalarAsync(); var scalarResult = await testCommand.ExecuteScalarAsync();
var tableCount = scalarResult != null ? (int)scalarResult : 0; var tableCount = scalarResult != null ? (int)scalarResult : 0;
Console.WriteLine($"Trovate {tableCount} tabelle utente nel database {currentDatabase}");
if (tableCount == 0) if (tableCount == 0)
{ {
return new Dictionary<string, IEnumerable<DbColumnInfo>>(); // Restituisce dizionario vuoto return new Dictionary<string, IEnumerable<DbColumnInfo>>(); // Restituisce dizionario vuoto
@@ -141,6 +157,236 @@ public class SqlServerSchemaProvider : IDatabaseSchemaProvider
return result; return result;
} }
/// <summary>
/// Ottiene la lista dei database disponibili sul server SQL Server
/// </summary>
public async Task<IEnumerable<string>> GetAvailableDatabasesAsync(string connectionString)
{
var databases = new List<string>();
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;
}
/// <summary>
/// Ottiene solo la lista dei nomi delle tabelle disponibili (senza dettagli delle colonne)
/// </summary>
public async Task<IEnumerable<string>> GetTableNamesAsync(string connectionString)
{
var tableNames = new List<string>();
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<string>();
}
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;
}
/// <summary>
/// Ottiene i dettagli delle colonne per una specifica tabella
/// </summary>
public async Task<IEnumerable<DbColumnInfo>> GetTableSchemaAsync(string connectionString, string tableName)
{
var columns = new List<DbColumnInfo>();
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<DbColumnInfo>();
}
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) private static string FormatSqlServerDataType(string dataType, int maxLength, byte precision, byte scale)
{ {
string formattedDataType = dataType; string formattedDataType = dataType;
@@ -65,6 +65,17 @@ public interface IDatabaseManager : IDisposable
/// </summary> /// </summary>
Task<IDictionary<string, IEnumerable<DbColumnInfo>>> GetDatabaseSchemaAsync(); Task<IDictionary<string, IEnumerable<DbColumnInfo>>> GetDatabaseSchemaAsync();
/// <summary>
/// Ottiene solo la lista dei nomi delle tabelle disponibili (senza dettagli delle colonne)
/// </summary>
Task<IEnumerable<string>> GetTableNamesAsync();
/// <summary>
/// Ottiene i dettagli delle colonne per una specifica tabella
/// </summary>
/// <param name="tableName">Nome della tabella (con schema se necessario)</param>
Task<IEnumerable<DbColumnInfo>> GetTableSchemaAsync(string tableName);
/// <summary> /// <summary>
/// Ottiene tutti i record da una tabella specifica come dizionari chiave-valore /// Ottiene tutti i record da una tabella specifica come dizionari chiave-valore
/// </summary> /// </summary>
@@ -14,4 +14,26 @@ public interface IDatabaseSchemaProvider
/// <param name="connectionString">Stringa di connessione al database</param> /// <param name="connectionString">Stringa di connessione al database</param>
/// <returns>Struttura gerarchica delle tabelle e delle loro colonne</returns> /// <returns>Struttura gerarchica delle tabelle e delle loro colonne</returns>
Task<IDictionary<string, IEnumerable<DbColumnInfo>>> GetDatabaseSchemaAsync(string connectionString); Task<IDictionary<string, IEnumerable<DbColumnInfo>>> GetDatabaseSchemaAsync(string connectionString);
/// <summary>
/// Ottiene la lista dei database disponibili sul server
/// </summary>
/// <param name="connectionString">Stringa di connessione al server (senza specificare il database)</param>
/// <returns>Lista dei nomi dei database disponibili</returns>
Task<IEnumerable<string>> GetAvailableDatabasesAsync(string connectionString);
/// <summary>
/// Ottiene solo la lista dei nomi delle tabelle disponibili (senza dettagli delle colonne)
/// </summary>
/// <param name="connectionString">Stringa di connessione al database</param>
/// <returns>Lista dei nomi delle tabelle</returns>
Task<IEnumerable<string>> GetTableNamesAsync(string connectionString);
/// <summary>
/// Ottiene i dettagli delle colonne per una specifica tabella
/// </summary>
/// <param name="connectionString">Stringa di connessione al database</param>
/// <param name="tableName">Nome della tabella (con schema se necessario)</param>
/// <returns>Lista delle informazioni sulle colonne</returns>
Task<IEnumerable<DbColumnInfo>> GetTableSchemaAsync(string connectionString, string tableName);
} }
+60 -59
View File
@@ -234,11 +234,11 @@
} }
</div> </div>
} }
else if (databaseTables.Any()) else if (availableTableNames.Any())
{ {
<!-- Sezione Tabelle (modalità standard) --> <!-- Sezione Tabelle (modalità standard) -->
<div class="mb-3"> <div class="mb-3">
<h6>Tabelle Database (@databaseTables.Count disponibili):</h6> <h6>Tabelle Database (@availableTableNames.Count disponibili):</h6>
<!-- Campo di ricerca --> <!-- Campo di ricerca -->
<div class="mb-2"> <div class="mb-2">
@@ -1131,7 +1131,7 @@
<div class="col-12"> <div class="col-12">
<ProfileSaver CanSave="CanSaveProfile()" <ProfileSaver CanSave="CanSaveProfile()"
SourceType="selectedSourceType" SourceType="selectedSourceType"
SourceSchema="@(databaseTables.Keys.FirstOrDefault()?.Split('.').FirstOrDefault())" SourceSchema="@(availableTableNames.FirstOrDefault()?.Split('.').FirstOrDefault())"
SourceTable="selectedTable" SourceTable="selectedTable"
DestinationType="rest" DestinationType="rest"
DestinationEndpoint="@(selectedRestEntity?.Name)" DestinationEndpoint="@(selectedRestEntity?.Name)"
@@ -1149,62 +1149,6 @@
OnProfileDeleted="OnProfileDeleted" OnProfileDeleted="OnProfileDeleted"
IsLoading="isLoadingProfiles" /> IsLoading="isLoadingProfiles" />
<!-- Modal per la selezione del database -->
@if (showDatabaseSelectionModal)
{
<div class="modal fade show d-block" style="background-color: rgba(0,0,0,0.5);">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">
<i class="fas fa-database"></i> Seleziona Database
</h5>
</div>
<div class="modal-body">
<p class="text-muted">
<i class="fas fa-info-circle"></i>
Sono stati trovati più database nel server. Seleziona il database da utilizzare:
</p>
@if (availableDatabases != null && availableDatabases.Any())
{
<div class="mb-3">
<label for="databaseSelect" class="form-label">Database disponibili:</label>
<select id="databaseSelect" class="form-select" @bind="selectedDatabase">
<option value="">-- Seleziona un database --</option>
@foreach (var db in availableDatabases)
{
<option value="@db">@db</option>
}
</select>
<small class="form-text text-muted">
<i class="fas fa-lightbulb"></i>
Il database determina quali tabelle e viste saranno disponibili per il mapping.
</small>
</div>
}
else
{
<div class="alert alert-warning">
<i class="fas fa-exclamation-triangle"></i>
Nessun database trovato o errore nel caricamento.
</div>
}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" @onclick="CancelDatabaseSelection">
<i class="fas fa-times"></i> Annulla
</button>
<button type="button" class="btn btn-primary" @onclick="OnDatabaseSelected"
disabled="@string.IsNullOrEmpty(selectedDatabase)">
<i class="fas fa-check"></i> Connetti al Database
</button>
</div>
</div>
</div>
</div>
}
<!-- Modal per la selezione dello schema --> <!-- Modal per la selezione dello schema -->
@if (showSchemaSelectionModal) @if (showSchemaSelectionModal)
{ {
@@ -1267,3 +1211,60 @@
</div> </div>
</div> </div>
} }
<!-- Modal per la selezione del database -->
@if (showDatabaseSelectionModal)
{
<div class="modal fade show d-block" style="background-color: rgba(0,0,0,0.5);">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">
<i class="fas fa-database"></i> Seleziona Database
</h5>
</div>
<div class="modal-body">
<p class="text-muted">
<i class="fas fa-info-circle"></i>
La connessione non specifica un database. Seleziona il database da esplorare:
</p>
@if (isLoadingDatabases)
{
<div class="text-center">
<div class="spinner-border spinner-border-sm me-2"></div>
Caricamento database...
</div>
}
else if (availableDatabases != null && availableDatabases.Any())
{
<div class="mb-3">
<label for="databaseSelect" class="form-label">Database disponibili:</label>
<select id="databaseSelect" class="form-select" @bind="selectedDatabase">
<option value="">-- Seleziona un database --</option>
@foreach (var db in availableDatabases)
{
<option value="@db">@db</option>
}
</select>
</div>
}
else
{
<div class="alert alert-warning">
<i class="fas fa-exclamation-triangle"></i>
Nessun database trovato o errore nel caricamento.
</div>
}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" @onclick="CancelDatabaseSelection">
<i class="fas fa-times"></i> Annulla
</button>
<button type="button" class="btn btn-primary" @onclick="OnDatabaseSelected" disabled="@string.IsNullOrEmpty(selectedDatabase)">
<i class="fas fa-check"></i> Connetti con Database
</button>
</div>
</div>
</div>
</div>
}
+300 -233
View File
@@ -57,19 +57,21 @@ public partial class DataCoupler : ComponentBase
private string restErrorMessage = ""; private string restErrorMessage = "";
// Database discovery // Database discovery
private Dictionary<string, IEnumerable<DbColumnInfo>> databaseTables = new(); private List<string> availableTableNames = new(); // Solo nomi delle tabelle
private Dictionary<string, IEnumerable<DbColumnInfo>> databaseTables = new(); // Schema dettagliato per tabelle caricate
private string selectedTable = ""; private string selectedTable = "";
private string databaseSearchTerm = ""; private string databaseSearchTerm = "";
// Database selection // Database selection - per gestire la selezione del database quando non specificato nella connection string
private List<string> availableDatabases = new(); private List<string> availableDatabases = new();
private List<string> availableSchemas = new();
private string selectedDatabase = ""; private string selectedDatabase = "";
private string selectedSchema = "";
private bool showDatabaseSelection = false;
private bool showDatabaseSelectionModal = false; private bool showDatabaseSelectionModal = false;
private bool showSchemaSelectionModal = false;
private bool isLoadingDatabases = false; private bool isLoadingDatabases = false;
// Database selection (schemas only)
private List<string> availableSchemas = new();
private string selectedSchema = "";
private bool showSchemaSelectionModal = false;
private bool isLoadingSchemas = false; private bool isLoadingSchemas = false;
// Custom query functionality // Custom query functionality
@@ -110,7 +112,6 @@ public partial class DataCoupler : ComponentBase
private string sourceKeyField = ""; // Campo che identifica univocamente il record sorgente private string sourceKeyField = ""; // Campo che identifica univocamente il record sorgente
private string suggestedPrimaryKey = ""; // Campo PK suggerito per database private string suggestedPrimaryKey = ""; // Campo PK suggerito per database
private bool requiresManualKeySelection = false; // Flag per indicare se è richiesta selezione manuale private bool requiresManualKeySelection = false; // Flag per indicare se è richiesta selezione manuale
private Dictionary<string, string> sourceKeyMappings = new(); // Per CSV: mapppatura colonna -> nome campo chiave
private bool useRecordAssociations = true; // Se utilizzare il sistema di associazioni private bool useRecordAssociations = true; // Se utilizzare il sistema di associazioni
// Trasferimento dati // Trasferimento dati
@@ -745,6 +746,12 @@ public partial class DataCoupler : ComponentBase
databaseSearchTerm = ""; databaseSearchTerm = "";
databaseErrorMessage = ""; databaseErrorMessage = "";
// Reset database selection
availableDatabases.Clear();
selectedDatabase = "";
showDatabaseSelectionModal = false;
isLoadingDatabases = false;
// Reset custom query state // Reset custom query state
useCustomQuery = false; useCustomQuery = false;
customQuery = ""; customQuery = "";
@@ -774,7 +781,7 @@ public partial class DataCoupler : ComponentBase
// Clear mappings when resetting REST state // Clear mappings when resetting REST state
ClearAllMappings(); ClearAllMappings();
}private async Task ConnectToDatabase() } private async Task ConnectToDatabase()
{ {
if (string.IsNullOrEmpty(selectedDatabaseCredential)) if (string.IsNullOrEmpty(selectedDatabaseCredential))
return; return;
@@ -783,7 +790,8 @@ public partial class DataCoupler : ComponentBase
databaseErrorMessage = ""; databaseErrorMessage = "";
try try
{ // Trova la credenziale {
// Trova la credenziale
var credential = databaseCredentials.FirstOrDefault(c => c.Name == selectedDatabaseCredential); var credential = databaseCredentials.FirstOrDefault(c => c.Name == selectedDatabaseCredential);
if (credential == null) if (credential == null)
{ {
@@ -797,42 +805,51 @@ public partial class DataCoupler : ComponentBase
{ {
databaseErrorMessage = $"Connessione fallita: {message}"; databaseErrorMessage = $"Connessione fallita: {message}";
return; 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); Logger.LogInformation("Creando database manager per credenziale: {CredentialName}", selectedDatabaseCredential);
currentDatabaseManager = await ConnectionFactory.CreateDatabaseManagerAsync(selectedDatabaseCredential); currentDatabaseManager = await ConnectionFactory.CreateDatabaseManagerAsync(selectedDatabaseCredential);
Logger.LogInformation("Database manager creato con successo"); Logger.LogInformation("Database manager creato con successo");
Logger.LogInformation("Iniziando discovery dello schema per database {DatabaseType} con credenziale: {CredentialName}", credential.DatabaseType, selectedDatabaseCredential); // Verifica se il database è specificato nella connection string
bool isDatabaseSpecified = await IsDatabaseSpecifiedInConnectionString(credential);
// Discovery dello schema con try-catch specifico if (isDatabaseSpecified)
try
{ {
var schema = await currentDatabaseManager.GetDatabaseSchemaAsync(); Logger.LogInformation("Database specificato nella connection string. Procedendo con discovery tabelle.");
try
Logger.LogInformation("Schema discovery completato. Tipo restituito: {SchemaType}, Numero elementi: {Count}",
schema?.GetType().Name ?? "null",
schema?.Count() ?? 0);
databaseTables = schema as Dictionary<string, IEnumerable<DbColumnInfo>> ??
(schema != null ? new Dictionary<string, IEnumerable<DbColumnInfo>>(schema) : new Dictionary<string, IEnumerable<DbColumnInfo>>());
Logger.LogInformation("Database tables dopo conversione: {Count} tabelle", databaseTables.Count);
if (databaseTables.Count == 0)
{ {
// Se non ci sono tabelle, potrebbe essere perché non è stato selezionato un database specifico await LoadTablesFromConnectedDatabase();
HandleDatabaseSelectionRequired(); 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; return;
} }
} }
catch (Exception schemaEx) else
{ {
Logger.LogError(schemaEx, "Errore specifico durante lo schema discovery"); Logger.LogInformation("Database non specificato nella connection string. Caricando database disponibili.");
databaseErrorMessage = $"Errore nello schema discovery: {schemaEx.Message}"; await LoadAvailableDatabases();
throw;
}
isDatabaseConnected = true; 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) catch (Exception ex)
{ {
@@ -842,8 +859,9 @@ public partial class DataCoupler : ComponentBase
finally finally
{ {
isConnectingDatabase = false; isConnectingDatabase = false;
StateHasChanged();
} }
} private async Task ConnectToRestApi() }private async Task ConnectToRestApi()
{ {
if (string.IsNullOrEmpty(selectedRestCredential)) if (string.IsNullOrEmpty(selectedRestCredential))
return; return;
@@ -926,6 +944,21 @@ public partial class DataCoupler : ComponentBase
suggestedPrimaryKey = ""; suggestedPrimaryKey = "";
requiresManualKeySelection = false; 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 it's a database source, try to detect the primary key
if (selectedSourceType == "database" && currentDatabaseManager != null) if (selectedSourceType == "database" && currentDatabaseManager != null)
{ {
@@ -987,9 +1020,9 @@ public partial class DataCoupler : ComponentBase
private IEnumerable<string> GetFilteredDatabaseTables() private IEnumerable<string> GetFilteredDatabaseTables()
{ {
if (string.IsNullOrEmpty(databaseSearchTerm)) if (string.IsNullOrEmpty(databaseSearchTerm))
return databaseTables.Keys; return availableTableNames;
return databaseTables.Keys.Where(table => return availableTableNames.Where(table =>
table.Contains(databaseSearchTerm, StringComparison.OrdinalIgnoreCase)); table.Contains(databaseSearchTerm, StringComparison.OrdinalIgnoreCase));
} }
@@ -1864,7 +1897,8 @@ public partial class DataCoupler : ComponentBase
if (databaseTables.Count == 0) if (databaseTables.Count == 0)
{ {
// Se non ci sono tabelle, potrebbe essere necessario selezionare un database specifico // 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 else
{ {
@@ -1923,50 +1957,6 @@ public partial class DataCoupler : ComponentBase
} }
} }
/// <summary>
/// Gestisce la situazione quando è richiesta la selezione di un database o schema specifico
/// </summary>
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}";
}
}
/// <summary> /// <summary>
/// Estrae lo schema dal nome completo di una tabella /// Estrae lo schema dal nome completo di una tabella
/// </summary> /// </summary>
@@ -2195,7 +2185,7 @@ public partial class DataCoupler : ComponentBase
{ {
return databaseType switch 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.Oracle => $"SELECT * FROM ({baseQuery}) WHERE ROWNUM <= {limit}",
DatabaseType.MySql => $"{baseQuery} LIMIT {limit}", DatabaseType.MySql => $"{baseQuery} LIMIT {limit}",
DatabaseType.PostgreSql => $"{baseQuery} LIMIT {limit}", DatabaseType.PostgreSql => $"{baseQuery} LIMIT {limit}",
@@ -2235,16 +2225,6 @@ public partial class DataCoupler : ComponentBase
return null; return null;
} }
/// <summary>
/// Annulla la selezione del database
/// </summary>
private void CancelDatabaseSelection()
{
showDatabaseSelectionModal = false;
selectedDatabase = "";
StateHasChanged();
}
/// <summary> /// <summary>
/// Annulla la selezione dello schema /// Annulla la selezione dello schema
/// </summary> /// </summary>
@@ -2255,106 +2235,6 @@ public partial class DataCoupler : ComponentBase
StateHasChanged(); StateHasChanged();
} }
/// <summary>
/// Conferma la selezione del database
/// </summary>
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<DbColumnInfo>();
}
}
}
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();
}
}
/// <summary> /// <summary>
/// Ottiene le colonne di una tabella specifica /// Ottiene le colonne di una tabella specifica
/// </summary> /// </summary>
@@ -2583,62 +2463,249 @@ public partial class DataCoupler : ComponentBase
} }
} }
/// <summary> // Gestione selezione database quando la discovery restituisce dizionario vuoto
/// Carica la lista dei database disponibili private async Task HandleDatabaseSelectionRequired()
/// </summary>
private async Task LoadAvailableDatabases()
{ {
if (currentDatabaseManager == null)
return;
isLoadingDatabases = true; isLoadingDatabases = true;
showDatabaseSelectionModal = true;
availableDatabases.Clear(); availableDatabases.Clear();
selectedDatabase = "";
try try
{ {
var credential = databaseCredentials.FirstOrDefault(c => c.Name == selectedDatabaseCredential); if (currentDatabaseManager != null)
if (credential == null) return;
string? databaseQuery = credential.DatabaseType switch
{ {
DatabaseType.SqlServer => "SELECT name FROM sys.databases WHERE name NOT IN ('master', 'tempdb', 'model', 'msdb')", var dbs = await currentDatabaseManager.GetAvailableDatabasesAsync();
DatabaseType.PostgreSql => "SELECT datname FROM pg_database WHERE datistemplate = false AND datname NOT IN ('postgres', 'template0', 'template1')", availableDatabases = dbs ?? new List<string>();
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));
}
}
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.LogError(ex, "Errore nel caricamento dei database disponibili"); databaseErrorMessage = $"Errore nel caricamento dei database: {ex.Message}";
} }
finally finally
{ {
isLoadingDatabases = false; 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<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
}
}
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<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
}
// 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;
}
}
} }
Binary file not shown.