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; using CredentialManager.Migrations; namespace DataConnection.EF; /// /// Implementazione di IExistingDatabaseManager basata su Entity Framework Core /// 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(); _options.DbContextConfigurator(optionsBuilder); _context = new ExistingDatabaseContext( optionsBuilder.Options, _options.ModelConfigurator, _options.EnableAutoDiscovery, _options.EntityAssembly, _options.EntityNamespace, _options.NamingStrategy); } public async Task TestConnectionAsync() { try { return await _context.Database.CanConnectAsync(); } catch { return false; } } public async Task> GetAsync( Expression>? filter = null, Func, IOrderedQueryable>? orderBy = null, string includeProperties = "", int? skip = null, int? take = null) where T : class { IQueryable query = _context.Set(); 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 GetByIdAsync(object id) where T : class { return await _context.Set().FindAsync(id); } public async Task> ExecuteQueryAsync(string sql, params object[] parameters) where T : class { return await _context.Set().FromSqlRaw(sql, parameters).ToListAsync(); } public async Task>> ExecuteRawQueryAsync(string sql, string databaseName, params object[] parameters) { try { // Assicurarsi che la connessione sia aperta if (_context.Database.GetDbConnection().State != ConnectionState.Open) { await _context.Database.OpenConnectionAsync(); } var connection = _context.Database.GetDbConnection(); // Debug: verifica il database attuale della connessione Console.WriteLine($"ExecuteRawQueryAsync: Database connessione prima: {connection.Database}"); Console.WriteLine($"ExecuteRawQueryAsync: Connection string: {connection.ConnectionString}"); // Estrai il database target dalla connection string del DbContext var connectionString = _context.Database.GetConnectionString(); Console.WriteLine($"ExecuteRawQueryAsync: Connection string completa: {connectionString}"); if (!string.IsNullOrEmpty(connectionString)) { //var builder = new SqlConnectionStringBuilder(connectionString); var targetDatabase = databaseName; Console.WriteLine($"ExecuteRawQueryAsync: InitialCatalog dalla connection string: '{targetDatabase}'"); Console.WriteLine($"ExecuteRawQueryAsync: Database corrente connessione: '{connection.Database}'"); // Se il database della connessione non corrisponde al target, forza il cambio if (!string.IsNullOrEmpty(targetDatabase) && !string.Equals(connection.Database, targetDatabase, StringComparison.OrdinalIgnoreCase)) { Console.WriteLine($"ExecuteRawQueryAsync: MISMATCH RILEVATO! Forzando cambio database da '{connection.Database}' a '{targetDatabase}'"); await connection.ChangeDatabaseAsync(targetDatabase); Console.WriteLine($"ExecuteRawQueryAsync: Database connessione dopo cambio: '{connection.Database}'"); } else { Console.WriteLine($"ExecuteRawQueryAsync: Database già corretto: '{connection.Database}' = '{targetDatabase}'"); } } else { Console.WriteLine("ExecuteRawQueryAsync: ATTENZIONE! Connection string è vuota!"); } using var command = connection.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); } var results = new List>(); using var reader = await command.ExecuteReaderAsync(); while (await reader.ReadAsync()) { var row = new Dictionary(); 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; } catch (Exception ex) { Console.WriteLine($"Errore nell'esecuzione della query raw: {ex.Message}"); throw; } } public async Task ExecuteCommandAsync(string sql, params object[] parameters) { return await _context.Database.ExecuteSqlRawAsync(sql, parameters); } public async Task>> GetDatabaseSchemaAsync() { try { // Assicurarsi che il contesto sia connesso if (_context.Database.GetDbConnection().State != ConnectionState.Open) { 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 utilizzando la connection string corrente del DbContext var connectionString = _context.Database.GetConnectionString(); if (connectionString == null) throw new InvalidOperationException("Connection string is null"); Console.WriteLine($"GetDatabaseSchemaAsync: Utilizzo connection string: {connectionString}"); var result = await schemaProvider.GetDatabaseSchemaAsync(connectionString); return result; } catch (Exception ex) { Console.WriteLine($"Errore nel recupero dello schema del database: {ex.Message}"); throw; } } /// /// 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 utilizzando la connection string corrente del DbContext 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 utilizzando la connection string corrente del DbContext 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 utilizzando la connection string corrente del DbContext 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 { var records = new List>(); // Usa la connessione del DbContext che è stata aggiornata se è stato chiamato ChangeDatabaseAsync if (_context.Database.GetDbConnection().State != ConnectionState.Open) { await _context.Database.OpenConnectionAsync(); } var connection = _context.Database.GetDbConnection(); // Debug: verifica il database attuale della connessione Console.WriteLine($"GetAllRecordsAsync: Database connessione prima: {connection.Database}"); // Estrai il database target dalla connection string del DbContext var connectionString = _context.Database.GetConnectionString(); Console.WriteLine($"GetAllRecordsAsync: Connection string completa: {connectionString}"); if (!string.IsNullOrEmpty(connectionString)) { var builder = new SqlConnectionStringBuilder(connectionString); var targetDatabase = builder.InitialCatalog; Console.WriteLine($"GetAllRecordsAsync: InitialCatalog dalla connection string: '{targetDatabase}'"); Console.WriteLine($"GetAllRecordsAsync: Database corrente connessione: '{connection.Database}'"); // Se il database della connessione non corrisponde al target, forza il cambio if (!string.IsNullOrEmpty(targetDatabase) && !string.Equals(connection.Database, targetDatabase, StringComparison.OrdinalIgnoreCase)) { Console.WriteLine($"GetAllRecordsAsync: MISMATCH RILEVATO! Forzando cambio database da '{connection.Database}' a '{targetDatabase}'"); await connection.ChangeDatabaseAsync(targetDatabase); Console.WriteLine($"GetAllRecordsAsync: Database connessione dopo cambio: '{connection.Database}'"); } else { Console.WriteLine($"GetAllRecordsAsync: Database già corretto: '{connection.Database}' = '{targetDatabase}'"); } } else { Console.WriteLine("GetAllRecordsAsync: ATTENZIONE! Connection string è vuota!"); } using var command = connection.CreateCommand(); // Query SQL per ottenere tutti i record - nessun limite // 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 * FROM {tableReference}"; using var reader = await command.ExecuteReaderAsync(); while (await reader.ReadAsync()) { var record = new Dictionary(); 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"); Console.WriteLine($"ChangeDatabaseAsync: Connessione corrente: {currentConnectionString}"); // Crea una nuova connection string con il database specificato var newConnectionString = UpdateConnectionStringDatabase(currentConnectionString, databaseName); Console.WriteLine($"ChangeDatabaseAsync: Nuova connessione: {newConnectionString}"); // Ricrea il contesto con la nuova connection string var optionsBuilder = new DbContextOptionsBuilder(); 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(); Console.WriteLine($"ChangeDatabaseAsync: Connessione aggiornata verificata: {_context.Database.GetConnectionString()}"); } catch (Exception ex) { Console.WriteLine($"Errore nel cambio database a '{databaseName}': {ex.Message}"); throw; } } /// /// Estrae la connection string del server (senza database specifico) da una connection string completa /// private string GetServerConnectionString(string connectionString) { var builder = new SqlConnectionStringBuilder(connectionString); builder.InitialCatalog = ""; // Rimuove il database specifico return builder.ConnectionString; } /// /// Aggiorna la connection string con un nuovo database /// private string UpdateConnectionStringDatabase(string connectionString, string databaseName) { var builder = new SqlConnectionStringBuilder(connectionString); builder.InitialCatalog = databaseName; return builder.ConnectionString; } /// /// Crea una connessione database appropriata in base al tipo di database /// private DbConnection CreateConnection(string connectionString) { switch (_options.DatabaseType) { case Enums.DatabaseType.SqlServer: return new SqlConnection(connectionString); case Enums.DatabaseType.Odbc: return new System.Data.Odbc.OdbcConnection(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 GetPrimaryKeyFieldAsync(string tableName) { try { // Usa la connessione del DbContext che è stata aggiornata se è stato chiamato ChangeDatabaseAsync if (_context.Database.GetDbConnection().State != ConnectionState.Open) { await _context.Database.OpenConnectionAsync(); } var connection = _context.Database.GetDbConnection(); // Debug: verifica il database attuale della connessione Console.WriteLine($"GetPrimaryKeyFieldAsync: Database connessione prima: {connection.Database}"); // Estrai il database target dalla connection string del DbContext var connectionString = _context.Database.GetConnectionString(); Console.WriteLine($"GetPrimaryKeyFieldAsync: Connection string completa: {connectionString}"); if (!string.IsNullOrEmpty(connectionString)) { var builder = new SqlConnectionStringBuilder(connectionString); var targetDatabase = builder.InitialCatalog; Console.WriteLine($"GetPrimaryKeyFieldAsync: InitialCatalog dalla connection string: '{targetDatabase}'"); Console.WriteLine($"GetPrimaryKeyFieldAsync: Database corrente connessione: '{connection.Database}'"); // Se il database della connessione non corrisponde al target, forza il cambio if (!string.IsNullOrEmpty(targetDatabase) && !string.Equals(connection.Database, targetDatabase, StringComparison.OrdinalIgnoreCase)) { Console.WriteLine($"GetPrimaryKeyFieldAsync: MISMATCH RILEVATO! Forzando cambio database da '{connection.Database}' a '{targetDatabase}'"); await connection.ChangeDatabaseAsync(targetDatabase); Console.WriteLine($"GetPrimaryKeyFieldAsync: Database connessione dopo cambio: '{connection.Database}'"); } else { Console.WriteLine($"GetPrimaryKeyFieldAsync: Database già corretto: '{connection.Database}' = '{targetDatabase}'"); } } else { Console.WriteLine("GetPrimaryKeyFieldAsync: ATTENZIONE! Connection string è vuota!"); } 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; } } /// public async Task UpsertRecordAsync(string tableName, string keyField, object? keyValue, Dictionary record) { try { if (_context.Database.GetDbConnection().State != ConnectionState.Open) await _context.Database.OpenConnectionAsync(); var connection = _context.Database.GetDbConnection(); // Determina il riferimento alla tabella (con o senza schema) string tableRef; if (tableName.Contains('.')) { var parts = tableName.Split('.', 2); tableRef = $"[{parts[0]}].[{parts[1]}]"; } else { tableRef = $"[{tableName}]"; } // Controlla se il record esiste già using var checkCmd = connection.CreateCommand(); checkCmd.CommandText = $"SELECT COUNT(*) FROM {tableRef} WHERE [{keyField}] = @p0"; var checkParam = checkCmd.CreateParameter(); checkParam.ParameterName = "@p0"; checkParam.Value = keyValue ?? DBNull.Value; checkCmd.Parameters.Add(checkParam); var countResult = await checkCmd.ExecuteScalarAsync(); bool exists = Convert.ToInt64(countResult ?? 0L) > 0; if (exists) { // UPDATE var fields = record.Keys.ToList(); var setClauses = fields.Select((f, i) => $"[{f}] = @p{i}").ToList(); var updateSql = $"UPDATE {tableRef} SET {string.Join(", ", setClauses)} WHERE [{keyField}] = @p{setClauses.Count}"; using var updateCmd = connection.CreateCommand(); updateCmd.CommandText = updateSql; for (int i = 0; i < fields.Count; i++) { var p = updateCmd.CreateParameter(); p.ParameterName = $"@p{i}"; p.Value = record[fields[i]] ?? DBNull.Value; updateCmd.Parameters.Add(p); } // Aggiunge il parametro per la WHERE var keyParam = updateCmd.CreateParameter(); keyParam.ParameterName = $"@p{fields.Count}"; keyParam.Value = keyValue ?? DBNull.Value; updateCmd.Parameters.Add(keyParam); await updateCmd.ExecuteNonQueryAsync(); } else { // INSERT var fields = record.Keys.ToList(); var fieldNames = string.Join(", ", fields.Select(f => $"[{f}]")); var paramPlaceholders = string.Join(", ", fields.Select((_, i) => $"@p{i}")); var insertSql = $"INSERT INTO {tableRef} ({fieldNames}) VALUES ({paramPlaceholders})"; using var insertCmd = connection.CreateCommand(); insertCmd.CommandText = insertSql; for (int i = 0; i < fields.Count; i++) { var p = insertCmd.CreateParameter(); p.ParameterName = $"@p{i}"; p.Value = record[fields[i]] ?? DBNull.Value; insertCmd.Parameters.Add(p); } await insertCmd.ExecuteNonQueryAsync(); } return true; } catch (Exception ex) { Console.WriteLine($"Errore nell'upsert in {tableName}: {ex.Message}"); return false; } } }