Files
Data-Coupler/DataConnection/DB/OdbcDatabaseManager.cs
Alessio Dal Santo 01f78466df [Feature] Implementazione completa supporto ODBC
- Aggiunta persistenza campi ODBC (OdbcDsnName, OdbcMode) in CredentialEntity
- Creata migration EF Core per nuovi campi database
- Aggiornato mapping credenziali per caricare/salvare dati ODBC
- Creato OdbcDatabaseManager dedicato (bypass EF Core che non supporta ODBC)
- Aggiornato DataConnectionFactory per usare OdbcDatabaseManager con connessioni ODBC
- Fix auto-load DSN: sostituito @onchange con @bind-Value:after in dropdown tipo database
- Fix test connessione SAP HANA: rimossa query SELECT 1 che causava errori sintassi
- Implementati tutti i metodi IDatabaseManager in OdbcDatabaseManager
- Supporto completo per discovery schema, tabelle e query ODBC

Risolve problema DbContext non configurato per ODBC e abilita connessioni ODBC complete.
2026-02-02 18:24:44 +01:00

354 lines
12 KiB
C#

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Odbc;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using DataConnection.EF.SchemaProviders;
using DataConnection.Interfaces;
namespace DataConnection.DB;
/// <summary>
/// Database manager per connessioni ODBC dirette (senza Entity Framework)
/// </summary>
public class OdbcDatabaseManager : IDatabaseManager
{
private readonly string _connectionString;
private readonly OdbcSchemaProvider _schemaProvider;
private string _currentDatabase = string.Empty;
public OdbcDatabaseManager(string connectionString)
{
_connectionString = connectionString ?? throw new ArgumentNullException(nameof(connectionString));
_schemaProvider = new OdbcSchemaProvider();
}
public async Task<bool> TestConnectionAsync()
{
try
{
using var connection = new OdbcConnection(_connectionString);
await connection.OpenAsync();
return true;
}
catch
{
return false;
}
}
public 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
{
throw new NotSupportedException("GetAsync<T> with LINQ expressions is not supported for ODBC. Use ExecuteQueryAsync instead.");
}
public Task<T?> GetByIdAsync<T>(object id) where T : class
{
throw new NotSupportedException("GetByIdAsync<T> is not supported for ODBC. Use ExecuteQueryAsync with WHERE clause instead.");
}
public Task<IEnumerable<T>> ExecuteQueryAsync<T>(string sql, params object[] parameters) where T : class
{
throw new NotSupportedException("ExecuteQueryAsync<T> with entity type is not supported for ODBC. Use ExecuteRawQueryAsync instead.");
}
public async Task<List<Dictionary<string, object>>> ExecuteRawQueryAsync(string sql, string databaseName = "", params object[] parameters)
{
var results = new List<Dictionary<string, object>>();
using var connection = new OdbcConnection(_connectionString);
await connection.OpenAsync();
// Cambia database se specificato
if (!string.IsNullOrEmpty(databaseName) && databaseName != _currentDatabase)
{
await connection.ChangeDatabaseAsync(databaseName);
_currentDatabase = databaseName;
}
using var command = new OdbcCommand(sql, connection);
// Aggiungi parametri
if (parameters != null && parameters.Length > 0)
{
for (int i = 0; i < parameters.Length; i++)
{
command.Parameters.Add(new OdbcParameter($"@p{i}", parameters[i] ?? DBNull.Value));
}
}
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 fieldName = reader.GetName(i);
var value = reader.IsDBNull(i) ? DBNull.Value : reader.GetValue(i);
row[fieldName] = value;
}
results.Add(row);
}
return results;
}
public async Task<int> ExecuteCommandAsync(string sql, params object[] parameters)
{
using var connection = new OdbcConnection(_connectionString);
await connection.OpenAsync();
using var command = new OdbcCommand(sql, connection);
if (parameters != null && parameters.Length > 0)
{
for (int i = 0; i < parameters.Length; i++)
{
command.Parameters.Add(new OdbcParameter($"@p{i}", parameters[i] ?? DBNull.Value));
}
}
return await command.ExecuteNonQueryAsync();
}
public async Task<List<string>> GetAvailableDatabasesAsync()
{
var databases = await _schemaProvider.GetAvailableDatabasesAsync(_connectionString);
return databases.ToList();
}
public async Task ChangeDatabaseAsync(string databaseName)
{
using var connection = new OdbcConnection(_connectionString);
await connection.OpenAsync();
await connection.ChangeDatabaseAsync(databaseName);
_currentDatabase = databaseName;
}
public async Task<IDictionary<string, IEnumerable<DbColumnInfo>>> GetDatabaseSchemaAsync()
{
return await _schemaProvider.GetDatabaseSchemaAsync(_connectionString);
}
public async Task<IEnumerable<string>> GetTableNamesAsync()
{
return await _schemaProvider.GetTableNamesAsync(_connectionString);
}
public async Task<IEnumerable<DbColumnInfo>> GetTableSchemaAsync(string tableName)
{
return await _schemaProvider.GetTableSchemaAsync(_connectionString, tableName);
}
public async Task<IEnumerable<Dictionary<string, object>>> GetAllRecordsAsync(string tableName)
{
var query = $"SELECT * FROM {tableName}";
var results = await ExecuteRawQueryAsync(query);
return results;
}
public async Task<string?> GetPrimaryKeyFieldAsync(string tableName)
{
try
{
var schema = await GetTableSchemaAsync(tableName);
var pkColumn = schema.FirstOrDefault(c => c.IsPrimaryKey);
return pkColumn?.Name;
}
catch
{
return null;
}
}
public async Task<IEnumerable<IDictionary<string, object?>>> ExecuteQueryAsync(string query, int? maxRows = null)
{
var results = new List<IDictionary<string, object?>>();
using var connection = new OdbcConnection(_connectionString);
await connection.OpenAsync();
using var command = new OdbcCommand(query, connection);
if (maxRows.HasValue)
{
command.CommandText = WrapQueryWithLimit(query, maxRows.Value);
}
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 fieldName = reader.GetName(i);
var value = reader.IsDBNull(i) ? null : reader.GetValue(i);
row[fieldName] = value;
}
results.Add(row);
}
return results;
}
public async Task<int> ExecuteNonQueryAsync(string query)
{
using var connection = new OdbcConnection(_connectionString);
await connection.OpenAsync();
using var command = new OdbcCommand(query, connection);
return await command.ExecuteNonQueryAsync();
}
public async Task<object?> ExecuteScalarAsync(string query)
{
using var connection = new OdbcConnection(_connectionString);
await connection.OpenAsync();
using var command = new OdbcCommand(query, connection);
return await command.ExecuteScalarAsync();
}
public async Task<int> InsertAsync(string tableName, IDictionary<string, object?> data)
{
var columns = string.Join(", ", data.Keys.Select(k => $"[{k}]"));
var parameters = string.Join(", ", data.Keys.Select((_, i) => $"?"));
var query = $"INSERT INTO {tableName} ({columns}) VALUES ({parameters})";
using var connection = new OdbcConnection(_connectionString);
await connection.OpenAsync();
using var command = new OdbcCommand(query, connection);
foreach (var value in data.Values)
{
command.Parameters.Add(new OdbcParameter { Value = value ?? DBNull.Value });
}
return await command.ExecuteNonQueryAsync();
}
public async Task<int> UpdateAsync(string tableName, IDictionary<string, object?> data, IDictionary<string, object?> whereClause)
{
var setClause = string.Join(", ", data.Keys.Select(k => $"[{k}] = ?"));
var whereConditions = string.Join(" AND ", whereClause.Keys.Select(k => $"[{k}] = ?"));
var query = $"UPDATE {tableName} SET {setClause} WHERE {whereConditions}";
using var connection = new OdbcConnection(_connectionString);
await connection.OpenAsync();
using var command = new OdbcCommand(query, connection);
// Aggiungi parametri SET
foreach (var value in data.Values)
{
command.Parameters.Add(new OdbcParameter { Value = value ?? DBNull.Value });
}
// Aggiungi parametri WHERE
foreach (var value in whereClause.Values)
{
command.Parameters.Add(new OdbcParameter { Value = value ?? DBNull.Value });
}
return await command.ExecuteNonQueryAsync();
}
public async Task<int> DeleteAsync(string tableName, IDictionary<string, object?> whereClause)
{
var whereConditions = string.Join(" AND ", whereClause.Keys.Select(k => $"[{k}] = ?"));
var query = $"DELETE FROM {tableName} WHERE {whereConditions}";
using var connection = new OdbcConnection(_connectionString);
await connection.OpenAsync();
using var command = new OdbcCommand(query, connection);
foreach (var value in whereClause.Values)
{
command.Parameters.Add(new OdbcParameter { Value = value ?? DBNull.Value });
}
return await command.ExecuteNonQueryAsync();
}
public async Task<int> BulkInsertAsync(string tableName, IEnumerable<IDictionary<string, object?>> dataList)
{
int totalInserted = 0;
using var connection = new OdbcConnection(_connectionString);
await connection.OpenAsync();
using var transaction = connection.BeginTransaction();
try
{
foreach (var data in dataList)
{
var columns = string.Join(", ", data.Keys.Select(k => $"[{k}]"));
var parameters = string.Join(", ", data.Keys.Select((_, i) => $"?"));
var query = $"INSERT INTO {tableName} ({columns}) VALUES ({parameters})";
using var command = new OdbcCommand(query, connection, transaction);
foreach (var value in data.Values)
{
command.Parameters.Add(new OdbcParameter { Value = value ?? DBNull.Value });
}
totalInserted += await command.ExecuteNonQueryAsync();
}
transaction.Commit();
}
catch
{
transaction.Rollback();
throw;
}
return totalInserted;
}
/// <summary>
/// Wrappa la query con LIMIT/TOP a seconda del dialetto SQL
/// Nota: ODBC non ha una sintassi standard, quindi usiamo TOP (SQL Server style)
/// che è supportato dalla maggior parte dei driver
/// </summary>
private string WrapQueryWithLimit(string query, int maxRows)
{
// Verifica se la query ha già un LIMIT o TOP
var upperQuery = query.Trim().ToUpperInvariant();
if (upperQuery.Contains("LIMIT ") || upperQuery.Contains("TOP "))
{
return query; // Query già limitata
}
// Prova con SELECT TOP (SQL Server, SAP HANA)
if (upperQuery.StartsWith("SELECT "))
{
return query.Insert(7, $"TOP {maxRows} ");
}
// Fallback: aggiungi LIMIT alla fine (MySQL, PostgreSQL style)
return $"{query} LIMIT {maxRows}";
}
public void Dispose()
{
// Nessuna risorsa da rilasciare per ODBC diretto
}
}