Files
Data-Coupler/DataConnection/DB/EF/EFCoreDatabaseManager.cs
T
Alessio 2238ddc4bf feat: ottimizza interfaccia query personalizzate rimuovendo messaggi verbosi
- Rimossi messaggi di successo per validazione query per ridurre ingombro visivo
- Eliminati alert informativi delle colonne rilevate dalla query
- Rimossa notificazione "Query validata!" nella sezione mapping
- Mantenuti solo i messaggi di errore quando necessario
- Migliorata UX con interfaccia più pulita e focalizzata sull'essenziale
- Funzionalità di estrazione colonne e mapping completamente preservata
2025-06-29 16:05:04 +02:00

415 lines
15 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;
}
} 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<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>
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;
}
}
}