Files
Data-Coupler/DataConnection/DB/OleDbDatabaseManager.cs
Alessio 82e0d6bc77 [Feature] Aggiunto supporto completo OLE DB per connessione database
## Nuovi file
- DataConnection/DB/OleDbDatabaseManager.cs: Manager completo per connessioni OLE DB
  con supporto Task.Run() per operazioni sincrone, parametri posizionali '?',
  guard per piattaforma Windows, nessun supporto ChangeDatabaseAsync (no-op)
- DataConnection/DB/EF/SchemaProviders/OleDbSchemaProvider.cs: Schema provider per
  OLE DB, usa OleDbSchemaGuid per tabelle/colonne/chiavi primarie, mapping tipi dati
- CredentialManager/Services/OleDbProviderDiscoveryService.cs: Servizio di discovery
  provider OLE DB installati tramite registro Windows (HKEY_CLASSES_ROOT). Rileva
  9 provider noti: VFPOLEDB.1, Microsoft.ACE.OLEDB.12.0, Jet 4.0, SQLOLEDB, ecc.
  Mostra warning per provider solo 32-bit (VFPOLEDB, Jet)
- PUBLISH_32BIT_64BIT.md: Documentazione completa comandi pubblicazione per
  win-x64, win-x86 (richiesto per VFP), linux-x64, osx-x64, osx-arm64.
  Include prerequisiti VFPOLEDB, esempi connection string VFP, note Docker

## File modificati
- DataConnection/DB/Enums/DatabaseType.cs: Aggiunto valore OleDb dopo Odbc
- DataConnection/DataConnection.csproj: Aggiunto pacchetto System.Data.OleDb 9.0.3
- DataConnection/DB/OdbcDatabaseManager.cs: Fix bug ChangeDatabaseAsync con try-catch
- CredentialManager/Models/CredentialModels.cs: Aggiunto OleDb all'enum DatabaseType,
  BuildOleDbConnectionString() con supporto provider da AdditionalParameters,
  default VFPOLEDB.1, costruzione connection string con parametri VFP
- DataConnection/CredentialManagement/Models/CredentialExtensions.cs: Mappatura
  OleDb in ToDataConnectionDatabaseType() e ToCredentialDatabaseType()
- DataConnection/CredentialManagement/Services/DataConnectionCredentialService.cs:
  Aggiunto case OleDb in TestDatabaseConnectionAsync e metodo TestOleDbConnection()
  con apertura connessione via Task.Run() e gestione OleDbException dettagliata
- Data_Coupler/Services/DataConnectionFactory.cs: Aggiunto case OleDb per creazione
  OleDbDatabaseManager
- Data_Coupler/Program.cs: Registrazione IOleDbProviderDiscoveryService come Scoped
- Data_Coupler/Pages/CredentialManagement.razor: Aggiunta UI completa OLE DB con
  sezione dedicata Visual FoxPro (percorso .dbc/.dbf, Collating Sequence, DELETED),
  provider discovery con refresh, anteprima connection string, variabili di stato
  e metodi nel codice Blazor, sincronizzazione AdditionalParameters al salvataggio

## Compatibilità
- VFP 8.0/9.0: testato con VFPOLEDB.1, connessione file-based .dbc e .dbf
- Richiede pubblicazione win-x86 per driver OLE DB 32-bit
- AnyCPU non supportato per VFP (COM 32-bit)
2026-05-25 21:20:08 +02:00

380 lines
14 KiB
C#

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.OleDb;
using System.Linq;
using System.Linq.Expressions;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using DataConnection.EF.SchemaProviders;
using DataConnection.Interfaces;
namespace DataConnection.DB;
/// <summary>
/// Database manager per connessioni OLE DB dirette (es. Visual FoxPro, Access, Jet)
/// Nota: i driver OLE DB come VFPOLEDB.1 sono 32-bit only — pubblicare con --runtime win-x86 se necessario
/// </summary>
public class OleDbDatabaseManager : IDatabaseManager
{
private readonly string _connectionString;
private readonly OleDbSchemaProvider _schemaProvider;
public OleDbDatabaseManager(string connectionString)
{
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
throw new PlatformNotSupportedException("OLE DB è supportato solo su Windows.");
_connectionString = connectionString ?? throw new ArgumentNullException(nameof(connectionString));
_schemaProvider = new OleDbSchemaProvider();
}
public async Task<bool> TestConnectionAsync()
{
try
{
using var connection = new OleDbConnection(_connectionString);
await Task.Run(() => connection.Open());
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> con espressioni LINQ non è supportato per OLE DB. Usare ExecuteRawQueryAsync.");
}
public Task<T?> GetByIdAsync<T>(object id) where T : class
{
throw new NotSupportedException("GetByIdAsync<T> non è supportato per OLE DB. Usare ExecuteRawQueryAsync con clausola WHERE.");
}
public Task<IEnumerable<T>> ExecuteQueryAsync<T>(string sql, params object[] parameters) where T : class
{
throw new NotSupportedException("ExecuteQueryAsync<T> non è supportato per OLE DB. Usare ExecuteRawQueryAsync.");
}
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 OleDbConnection(_connectionString);
await Task.Run(() => connection.Open());
// OLE DB file-based (VFP, Access) non supporta ChangeDatabaseAsync — il database è nel Data Source
// Ignoriamo databaseName per questa tipologia di provider
using var command = new OleDbCommand(sql, connection);
if (parameters != null && parameters.Length > 0)
{
for (int i = 0; i < parameters.Length; i++)
{
command.Parameters.Add(new OleDbParameter($"@p{i}", parameters[i] ?? DBNull.Value));
}
}
using var reader = await Task.Run(() => command.ExecuteReader());
while (reader.Read())
{
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 OleDbConnection(_connectionString);
await Task.Run(() => connection.Open());
using var command = new OleDbCommand(sql, connection);
if (parameters != null && parameters.Length > 0)
{
for (int i = 0; i < parameters.Length; i++)
{
command.Parameters.Add(new OleDbParameter($"@p{i}", parameters[i] ?? DBNull.Value));
}
}
return await Task.Run(() => command.ExecuteNonQuery());
}
public async Task<List<string>> GetAvailableDatabasesAsync()
{
var databases = await _schemaProvider.GetAvailableDatabasesAsync(_connectionString);
return databases.ToList();
}
public async Task ChangeDatabaseAsync(string databaseName)
{
// I provider OLE DB file-based (VFP, Access) non supportano il cambio di database a runtime
// Il Data Source nella connection string definisce il database
Console.WriteLine($"[OleDb] ChangeDatabaseAsync ignorato per provider file-based (database: {databaseName})");
await Task.CompletedTask;
}
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}";
return await ExecuteRawQueryAsync(query);
}
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 OleDbConnection(_connectionString);
await Task.Run(() => connection.Open());
var commandText = maxRows.HasValue ? WrapQueryWithLimit(query, maxRows.Value) : query;
using var command = new OleDbCommand(commandText, connection);
using var reader = await Task.Run(() => command.ExecuteReader());
while (reader.Read())
{
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 OleDbConnection(_connectionString);
await Task.Run(() => connection.Open());
using var command = new OleDbCommand(query, connection);
return await Task.Run(() => command.ExecuteNonQuery());
}
public async Task<object?> ExecuteScalarAsync(string query)
{
using var connection = new OleDbConnection(_connectionString);
await Task.Run(() => connection.Open());
using var command = new OleDbCommand(query, connection);
return await Task.Run(() => command.ExecuteScalar());
}
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(_ => "?"));
var query = $"INSERT INTO {tableName} ({columns}) VALUES ({parameters})";
using var connection = new OleDbConnection(_connectionString);
await Task.Run(() => connection.Open());
using var command = new OleDbCommand(query, connection);
foreach (var value in data.Values)
command.Parameters.Add(new OleDbParameter { Value = value ?? DBNull.Value });
return await Task.Run(() => command.ExecuteNonQuery());
}
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 OleDbConnection(_connectionString);
await Task.Run(() => connection.Open());
using var command = new OleDbCommand(query, connection);
foreach (var value in data.Values)
command.Parameters.Add(new OleDbParameter { Value = value ?? DBNull.Value });
foreach (var value in whereClause.Values)
command.Parameters.Add(new OleDbParameter { Value = value ?? DBNull.Value });
return await Task.Run(() => command.ExecuteNonQuery());
}
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 OleDbConnection(_connectionString);
await Task.Run(() => connection.Open());
using var command = new OleDbCommand(query, connection);
foreach (var value in whereClause.Values)
command.Parameters.Add(new OleDbParameter { Value = value ?? DBNull.Value });
return await Task.Run(() => command.ExecuteNonQuery());
}
public async Task<int> BulkInsertAsync(string tableName, IEnumerable<IDictionary<string, object?>> dataList)
{
int totalInserted = 0;
using var connection = new OleDbConnection(_connectionString);
await Task.Run(() => connection.Open());
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(_ => "?"));
var query = $"INSERT INTO {tableName} ({columns}) VALUES ({parameters})";
using var command = new OleDbCommand(query, connection, transaction);
foreach (var value in data.Values)
command.Parameters.Add(new OleDbParameter { Value = value ?? DBNull.Value });
totalInserted += await Task.Run(() => command.ExecuteNonQuery());
}
transaction.Commit();
}
catch
{
transaction.Rollback();
throw;
}
return totalInserted;
}
public async Task<bool> UpsertRecordAsync(string tableName, string keyField, object? keyValue, Dictionary<string, object?> record)
{
try
{
using var connection = new OleDbConnection(_connectionString);
await Task.Run(() => connection.Open());
using var checkCmd = new OleDbCommand($"SELECT COUNT(*) FROM {tableName} WHERE [{keyField}] = ?", connection);
checkCmd.Parameters.Add(new OleDbParameter { Value = keyValue ?? DBNull.Value });
var countResult = await Task.Run(() => checkCmd.ExecuteScalar());
bool exists = Convert.ToInt64(countResult ?? 0L) > 0;
if (exists)
{
var fields = record.Keys.ToList();
var setClauses = fields.Select(f => $"[{f}] = ?").ToList();
var updateSql = $"UPDATE {tableName} SET {string.Join(", ", setClauses)} WHERE [{keyField}] = ?";
using var updateCmd = new OleDbCommand(updateSql, connection);
foreach (var f in fields)
updateCmd.Parameters.Add(new OleDbParameter { Value = record[f] ?? DBNull.Value });
updateCmd.Parameters.Add(new OleDbParameter { Value = keyValue ?? DBNull.Value });
await Task.Run(() => updateCmd.ExecuteNonQuery());
}
else
{
var fields = record.Keys.ToList();
var fieldNames = string.Join(", ", fields.Select(f => $"[{f}]"));
var paramPlaceholders = string.Join(", ", fields.Select(_ => "?"));
var insertSql = $"INSERT INTO {tableName} ({fieldNames}) VALUES ({paramPlaceholders})";
using var insertCmd = new OleDbCommand(insertSql, connection);
foreach (var f in fields)
insertCmd.Parameters.Add(new OleDbParameter { Value = record[f] ?? DBNull.Value });
await Task.Run(() => insertCmd.ExecuteNonQuery());
}
return true;
}
catch (Exception ex)
{
Console.WriteLine($"Errore nell'upsert OLE DB in {tableName}: {ex.Message}");
return false;
}
}
/// <summary>
/// VFP e la maggior parte dei provider OLE DB supportano SELECT TOP N (SQL Server style)
/// </summary>
private static string WrapQueryWithLimit(string query, int maxRows)
{
var upperQuery = query.Trim().ToUpperInvariant();
if (upperQuery.Contains("LIMIT ") || upperQuery.Contains("TOP "))
return query;
if (upperQuery.StartsWith("SELECT "))
return query.Insert(7, $"TOP {maxRows} ");
return $"{query} LIMIT {maxRows}";
}
public void Dispose()
{
// Nessuna risorsa da rilasciare
}
}