77efe986a0
- 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
459 lines
17 KiB
C#
459 lines
17 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Data;
|
|
using System.Data.Common;
|
|
using System.Linq;
|
|
using System.Linq.Expressions;
|
|
using System.Threading.Tasks;
|
|
using DataConnection.Interfaces;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
|
using Microsoft.EntityFrameworkCore.Metadata;
|
|
using Microsoft.Data.SqlClient;
|
|
|
|
namespace DataConnection.EF;
|
|
|
|
/// <summary>
|
|
/// Implementazione di IExistingDatabaseManager basata su Entity Framework Core
|
|
/// </summary>
|
|
public class EFCoreDatabaseManager : IDatabaseManager
|
|
{
|
|
private readonly DbManagerOptions _options;
|
|
private ExistingDatabaseContext _context = null!;
|
|
|
|
public EFCoreDatabaseManager(DbManagerOptions options)
|
|
{
|
|
_options = options ?? throw new ArgumentNullException(nameof(options));
|
|
InitializeContext();
|
|
}
|
|
|
|
private void InitializeContext()
|
|
{
|
|
var optionsBuilder = new DbContextOptionsBuilder<ExistingDatabaseContext>();
|
|
_options.DbContextConfigurator(optionsBuilder);
|
|
|
|
_context = new ExistingDatabaseContext(
|
|
optionsBuilder.Options,
|
|
_options.ModelConfigurator,
|
|
_options.EnableAutoDiscovery,
|
|
_options.EntityAssembly,
|
|
_options.EntityNamespace,
|
|
_options.NamingStrategy);
|
|
}
|
|
|
|
public async Task<bool> TestConnectionAsync()
|
|
{
|
|
try
|
|
{
|
|
return await _context.Database.CanConnectAsync();
|
|
}
|
|
catch
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public async Task<IEnumerable<T>> GetAsync<T>(
|
|
Expression<Func<T, bool>>? filter = null,
|
|
Func<IQueryable<T>, IOrderedQueryable<T>>? orderBy = null,
|
|
string includeProperties = "",
|
|
int? skip = null,
|
|
int? take = null) where T : class
|
|
{
|
|
IQueryable<T> query = _context.Set<T>();
|
|
|
|
if (filter != null)
|
|
{
|
|
query = query.Where(filter);
|
|
}
|
|
|
|
foreach (var includeProperty in includeProperties.Split
|
|
(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
|
|
{
|
|
query = query.Include(includeProperty);
|
|
}
|
|
|
|
if (orderBy != null)
|
|
{
|
|
query = orderBy(query);
|
|
}
|
|
|
|
if (skip.HasValue)
|
|
{
|
|
query = query.Skip(skip.Value);
|
|
}
|
|
|
|
if (take.HasValue)
|
|
{
|
|
query = query.Take(take.Value);
|
|
}
|
|
|
|
return await query.ToListAsync();
|
|
}
|
|
|
|
public async Task<T?> GetByIdAsync<T>(object id) where T : class
|
|
{
|
|
return await _context.Set<T>().FindAsync(id);
|
|
}
|
|
|
|
public async Task<IEnumerable<T>> ExecuteQueryAsync<T>(string sql, params object[] parameters) where T : class
|
|
{
|
|
return await _context.Set<T>().FromSqlRaw(sql, parameters).ToListAsync();
|
|
}
|
|
|
|
public async Task<List<Dictionary<string, object>>> ExecuteRawQueryAsync(string sql, params object[] parameters)
|
|
{
|
|
using var command = _context.Database.GetDbConnection().CreateCommand();
|
|
command.CommandText = sql;
|
|
|
|
// Aggiungi i parametri
|
|
for (int i = 0; i < parameters.Length; i++)
|
|
{
|
|
var parameter = command.CreateParameter();
|
|
parameter.ParameterName = $"@p{i}";
|
|
parameter.Value = parameters[i] ?? DBNull.Value;
|
|
command.Parameters.Add(parameter);
|
|
}
|
|
|
|
await _context.Database.OpenConnectionAsync();
|
|
|
|
var results = new List<Dictionary<string, object>>();
|
|
|
|
using var reader = await command.ExecuteReaderAsync();
|
|
|
|
while (await reader.ReadAsync())
|
|
{
|
|
var row = new Dictionary<string, object>();
|
|
|
|
for (int i = 0; i < reader.FieldCount; i++)
|
|
{
|
|
var columnName = reader.GetName(i);
|
|
var value = reader.IsDBNull(i) ? null : reader.GetValue(i);
|
|
row[columnName] = value ?? ""; // Usa stringa vuota invece di null
|
|
}
|
|
|
|
results.Add(row);
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
public async Task<int> ExecuteCommandAsync(string sql, params object[] parameters)
|
|
{
|
|
return await _context.Database.ExecuteSqlRawAsync(sql, parameters);
|
|
}
|
|
|
|
public async Task<IDictionary<string, IEnumerable<DbColumnInfo>>> GetDatabaseSchemaAsync()
|
|
{
|
|
try
|
|
{
|
|
// Assicurarsi che il contesto sia connesso
|
|
await _context.Database.OpenConnectionAsync();
|
|
|
|
// 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
|
|
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}");
|
|
throw;
|
|
}
|
|
}
|
|
|
|
/// <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
|
|
{
|
|
var records = new List<Dictionary<string, object>>();
|
|
|
|
// Usa la stessa connection string utilizzata per il discovery dello schema
|
|
var connectionString = _context.Database.GetConnectionString();
|
|
if (connectionString == null)
|
|
throw new InvalidOperationException("Connection string is null");
|
|
|
|
// Determina il tipo di connessione in base al DatabaseType
|
|
using var connection = CreateConnection(connectionString);
|
|
await connection.OpenAsync();
|
|
|
|
using var command = connection.CreateCommand();
|
|
|
|
// Query SQL semplice per ottenere tutti i record - limitiamo a 1000 per sicurezza
|
|
// Se il nome della tabella contiene già lo schema (es. "dbo.OCRD"), lo usiamo così com'è
|
|
// Altrimenti aggiungiamo le parentesi quadre
|
|
string tableReference;
|
|
if (tableName.Contains('.'))
|
|
{
|
|
// Il nome contiene già lo schema, separiamo e mettiamo entrambi tra parentesi quadre
|
|
var parts = tableName.Split('.');
|
|
tableReference = $"[{parts[0]}].[{parts[1]}]";
|
|
}
|
|
else
|
|
{
|
|
// Solo il nome della tabella, usiamo le parentesi quadre
|
|
tableReference = $"[{tableName}]";
|
|
}
|
|
|
|
command.CommandText = $"SELECT TOP 1000 * FROM {tableReference}";
|
|
|
|
using var reader = await command.ExecuteReaderAsync();
|
|
|
|
while (await reader.ReadAsync())
|
|
{
|
|
var record = new Dictionary<string, object>();
|
|
|
|
for (int i = 0; i < reader.FieldCount; i++)
|
|
{
|
|
var columnName = reader.GetName(i);
|
|
var value = reader.IsDBNull(i) ? null : reader.GetValue(i);
|
|
record[columnName] = value!;
|
|
}
|
|
|
|
records.Add(record);
|
|
}
|
|
|
|
return records;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"Errore nell'ottenere i record dalla tabella {tableName}: {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>
|
|
private DbConnection CreateConnection(string connectionString)
|
|
{
|
|
switch (_options.DatabaseType)
|
|
{
|
|
case Enums.DatabaseType.SqlServer:
|
|
return new SqlConnection(connectionString);
|
|
// Aggiungi altri tipi di database quando necessario
|
|
// case Enums.DatabaseType.MySQL:
|
|
// return new MySqlConnection(connectionString);
|
|
// case Enums.DatabaseType.PostgreSQL:
|
|
// return new NpgsqlConnection(connectionString);
|
|
default:
|
|
throw new NotSupportedException($"Database type {_options.DatabaseType} is not supported for direct connections");
|
|
}
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
_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;
|
|
}
|
|
}
|
|
}
|