75a9bbb0c8
- Rimosso limite TOP 1000 in EFCoreDatabaseManager.GetAllRecordsAsync - Eliminati controlli di sicurezza con limiti automatici in DataCoupler - Aggiornata documentazione per riflettere estrazione senza limiti - Supporto completo per dataset di grandi dimensioni - Mantenuto batching automatico Salesforce (25 record/batch) in parallelo Ora il sistema supporta l'estrazione completa di tabelle e query custom senza restrizioni artificiali, ideale per migrazioni e use cases enterprise.
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 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<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;
|
|
}
|
|
}
|
|
}
|