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;
}
}
}