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; /// /// 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, 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>(); 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; } 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 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; } } public async Task>> GetAllRecordsAsync(string tableName) { try { var records = new List>(); // 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(); 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> GetAvailableDatabasesAsync() { try { var connectionString = _context.Database.GetConnectionString(); if (connectionString == null) throw new InvalidOperationException("Connection string is null"); // Crea una connessione al server (senza specificare il database) var serverConnectionString = GetServerConnectionString(connectionString); using var connection = CreateConnection(serverConnectionString); await connection.OpenAsync(); using var command = connection.CreateCommand(); // Query per ottenere i database disponibili (esclude quelli di sistema) command.CommandText = @" SELECT name FROM sys.databases WHERE state_desc = 'ONLINE' AND name NOT IN ('master', 'tempdb', 'model', 'msdb', 'distribution') ORDER BY name"; var databases = new List(); using var reader = await command.ExecuteReaderAsync(); while (await reader.ReadAsync()) { databases.Add(reader.GetString(0)); } return databases; } catch (Exception ex) { Console.WriteLine($"Errore nell'ottenere la lista dei database: {ex.Message}"); throw; } } public async Task ChangeDatabaseAsync(string databaseName) { try { 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(); 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; } } /// /// 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); // 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 { 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; } } }