feat: Implementa gestione intelligente della chiave sorgente con rilevamento PK

- Aggiunge rilevamento automatico Primary Key per connessioni database
- Rimuove completamente il fallback automatico per lato sorgente
- Implementa selezione manuale obbligatoria per file e sorgenti non-DB
- Migliora UI con suggerimenti intelligenti e feedback visivo
- Aggiunge validazione multi-livello (UI, pre-transfer, runtime)
- Introduce metodo GetPrimaryKeyFieldAsync in IDatabaseManager
- Modifica GenerateSourceKey per richiedere sempre campo specifico
- Implementa controllo IsTransferButtonEnabled per validazione form

Breaking changes:
- La generazione automatica delle chiavi sorgente è stata rimossa
- Il campo chiave sorgente è ora obbligatorio quando si usa il sistema associazioni

Fixes: Risolve problema di discovery schema vuoto con selezione database
This commit is contained in:
2025-06-28 02:05:59 +02:00
parent 207d6fc845
commit 51c61eabf7
29 changed files with 2748 additions and 104 deletions
+172 -20
View File
@@ -109,35 +109,26 @@ public class EFCoreDatabaseManager : IDatabaseManager
{
try
{
Console.WriteLine($"[DEBUG] Iniziando GetDatabaseSchemaAsync - DatabaseType: {_options.DatabaseType}");
// Assicurarsi che il contesto sia connesso
await _context.Database.OpenConnectionAsync();
Console.WriteLine($"[DEBUG] Connessione al database aperta. Connection string: {_context.Database.GetConnectionString()}");
// Usa la factory per ottenere il provider appropriato in base al tipo di database
var schemaProvider = DatabaseSchemaProviderFactory.CreateProvider(_options.DatabaseType);
Console.WriteLine($"[DEBUG] Schema provider creato: {schemaProvider.GetType().Name}");
// Usa il provider per ottenere lo schema
var result = await schemaProvider.GetDatabaseSchemaAsync(_context.Database.GetConnectionString());
Console.WriteLine($"[DEBUG] Schema ottenuto. Numero tabelle: {result?.Count ?? 0}");
if (result != null && result.Count > 0)
{
foreach (var table in result.Take(3))
{
Console.WriteLine($"[DEBUG] Tabella: {table.Key}, Colonne: {table.Value?.Count() ?? 0}");
}
}
var connectionString = _context.Database.GetConnectionString();
if (connectionString == null)
throw new InvalidOperationException("Connection string is null");
var result = await schemaProvider.GetDatabaseSchemaAsync(connectionString);
return result;
}
catch (Exception ex)
{
Console.WriteLine($"Errore nel recupero dello schema del database: {ex.Message}");
Console.WriteLine($"[DEBUG] Stack trace: {ex.StackTrace}");
throw; }
throw;
}
} public async Task<IEnumerable<Dictionary<string, object>>> GetAllRecordsAsync(string tableName)
{
try
@@ -146,7 +137,8 @@ public class EFCoreDatabaseManager : IDatabaseManager
// Usa la stessa connection string utilizzata per il discovery dello schema
var connectionString = _context.Database.GetConnectionString();
Console.WriteLine($"[DEBUG] GetAllRecordsAsync - Using connection string: {connectionString?.Substring(0, Math.Min(50, connectionString?.Length ?? 0))}...");
if (connectionString == null)
throw new InvalidOperationException("Connection string is null");
// Determina il tipo di connessione in base al DatabaseType
using var connection = CreateConnection(connectionString);
@@ -171,7 +163,6 @@ public class EFCoreDatabaseManager : IDatabaseManager
}
command.CommandText = $"SELECT TOP 1000 * FROM {tableReference}";
Console.WriteLine($"[DEBUG] GetAllRecordsAsync - Query: {command.CommandText}");
using var reader = await command.ExecuteReaderAsync();
@@ -183,13 +174,12 @@ public class EFCoreDatabaseManager : IDatabaseManager
{
var columnName = reader.GetName(i);
var value = reader.IsDBNull(i) ? null : reader.GetValue(i);
record[columnName] = value;
record[columnName] = value!;
}
records.Add(record);
}
Console.WriteLine($"[DEBUG] GetAllRecordsAsync - Tabella: {tableName}, Record ottenuti: {records.Count}");
return records;
}
catch (Exception ex)
@@ -199,6 +189,114 @@ 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)
{
try
{
var currentConnectionString = _context.Database.GetConnectionString();
if (currentConnectionString == null)
throw new InvalidOperationException("Connection string is null");
// Crea una nuova connection string con il database specificato
var newConnectionString = UpdateConnectionStringDatabase(currentConnectionString, databaseName);
// Ricrea il contesto con la nuova connection string
var optionsBuilder = new DbContextOptionsBuilder<ExistingDatabaseContext>();
switch (_options.DatabaseType)
{
case Enums.DatabaseType.SqlServer:
optionsBuilder.UseSqlServer(newConnectionString, options =>
{
if (_options.CommandTimeout > 0)
options.CommandTimeout(_options.CommandTimeout);
});
break;
default:
throw new NotSupportedException($"Database type {_options.DatabaseType} is not supported");
}
// Disponi il vecchio contesto e crea quello nuovo
_context.Dispose();
_context = new ExistingDatabaseContext(
optionsBuilder.Options,
_options.ModelConfigurator,
_options.EnableAutoDiscovery,
_options.EntityAssembly,
_options.EntityNamespace,
_options.NamingStrategy);
// Testa la connessione al nuovo database
await _context.Database.OpenConnectionAsync();
}
catch (Exception ex)
{
Console.WriteLine($"Errore nel cambio database a '{databaseName}': {ex.Message}");
throw;
}
}
/// <summary>
/// Estrae la connection string del server (senza database specifico) da una connection string completa
/// </summary>
private string GetServerConnectionString(string connectionString)
{
var builder = new SqlConnectionStringBuilder(connectionString);
builder.InitialCatalog = ""; // Rimuove il database specifico
return builder.ConnectionString;
}
/// <summary>
/// Aggiorna la connection string con un nuovo database
/// </summary>
private string UpdateConnectionStringDatabase(string connectionString, string databaseName)
{
var builder = new SqlConnectionStringBuilder(connectionString);
builder.InitialCatalog = databaseName;
return builder.ConnectionString;
}
/// <summary>
/// Crea una connessione database appropriata in base al tipo di database
/// </summary>
@@ -222,4 +320,58 @@ public class EFCoreDatabaseManager : IDatabaseManager
{
_context?.Dispose();
}
public async Task<string?> GetPrimaryKeyFieldAsync(string tableName)
{
try
{
var connectionString = _context.Database.GetConnectionString();
if (connectionString == null)
throw new InvalidOperationException("Connection string is null");
using var connection = CreateConnection(connectionString);
await connection.OpenAsync();
using var command = connection.CreateCommand();
// Query per ottenere la Primary Key della tabella
// Gestisce anche tabelle con schema (es. "dbo.TableName")
string schemaName = "dbo"; // Default schema
string tableNameOnly = tableName;
if (tableName.Contains('.'))
{
var parts = tableName.Split('.');
schemaName = parts[0];
tableNameOnly = parts[1];
}
command.CommandText = @"
SELECT COLUMN_NAME
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
WHERE OBJECTPROPERTY(OBJECT_ID(CONSTRAINT_SCHEMA + '.' + QUOTENAME(CONSTRAINT_NAME)), 'IsPrimaryKey') = 1
AND TABLE_SCHEMA = @schemaName
AND TABLE_NAME = @tableName
ORDER BY ORDINAL_POSITION";
// Usa 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);
var result = await command.ExecuteScalarAsync();
return result?.ToString();
}
catch (Exception ex)
{
Console.WriteLine($"Errore nel recupero della Primary Key per la tabella {tableName}: {ex.Message}");
return null;
}
}
}
@@ -17,13 +17,24 @@ public class SqlServerSchemaProvider : IDatabaseSchemaProvider
try
{
Console.WriteLine($"[DEBUG] SqlServerSchemaProvider - Connection string: {connectionString?.Substring(0, Math.Min(50, connectionString?.Length ?? 0))}...");
using (var connection = new SqlConnection(connectionString))
{
await connection.OpenAsync();
Console.WriteLine($"[DEBUG] SqlServerSchemaProvider - Connessione aperta");
// Prima verifichiamo se ci sono tabelle utente con una query semplice
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;
if (tableCount == 0)
{
return new Dictionary<string, IEnumerable<DbColumnInfo>>(); // Restituisce dizionario vuoto
}
}
// Se ci sono tabelle, procediamo con la query completa
// Query per ottenere la struttura delle tabelle in SQL Server
string sql = @"
SELECT
@@ -71,8 +82,8 @@ public class SqlServerSchemaProvider : IDatabaseSchemaProvider
using (var reader = await command.ExecuteReaderAsync())
{
string currentTable = null;
List<DbColumnInfo> columns = null;
string? currentTable = null;
List<DbColumnInfo>? columns = null;
while (await reader.ReadAsync())
{
@@ -117,12 +128,6 @@ public class SqlServerSchemaProvider : IDatabaseSchemaProvider
{
result[currentTable] = columns;
}
Console.WriteLine($"[DEBUG] SqlServerSchemaProvider - Query completata. Trovate {result.Count} tabelle");
foreach (var table in result.Take(3))
{
Console.WriteLine($"[DEBUG] SqlServerSchemaProvider - Tabella: {table.Key}, Colonne: {table.Value?.Count() ?? 0}");
}
}
}
}