From 82e0d6bc77b2a3da06ad3bd65aaca171f52a2afd Mon Sep 17 00:00:00 2001 From: Alessio Dal Santo Date: Mon, 25 May 2026 21:20:08 +0200 Subject: [PATCH] [Feature] Aggiunto supporto completo OLE DB per connessione database MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 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) --- CredentialManager/Models/CredentialModels.cs | 45 ++- .../Services/OleDbProviderDiscoveryService.cs | 112 ++++++ .../Models/CredentialExtensions.cs | 2 + .../DataConnectionCredentialService.cs | 41 ++ .../EF/SchemaProviders/OleDbSchemaProvider.cs | 335 ++++++++++++++++ DataConnection/DB/Enums/DatabaseType.cs | 3 +- DataConnection/DB/OdbcDatabaseManager.cs | 14 +- DataConnection/DB/OleDbDatabaseManager.cs | 379 ++++++++++++++++++ DataConnection/DataConnection.csproj | 1 + Data_Coupler/Pages/CredentialManagement.razor | 242 +++++++++++ Data_Coupler/Program.cs | 1 + .../Services/DataConnectionFactory.cs | 8 + PUBLISH_32BIT_64BIT.md | 164 ++++++++ 13 files changed, 1342 insertions(+), 5 deletions(-) create mode 100644 CredentialManager/Services/OleDbProviderDiscoveryService.cs create mode 100644 DataConnection/DB/EF/SchemaProviders/OleDbSchemaProvider.cs create mode 100644 DataConnection/DB/OleDbDatabaseManager.cs create mode 100644 PUBLISH_32BIT_64BIT.md diff --git a/CredentialManager/Models/CredentialModels.cs b/CredentialManager/Models/CredentialModels.cs index f0aa21a..3285416 100644 --- a/CredentialManager/Models/CredentialModels.cs +++ b/CredentialManager/Models/CredentialModels.cs @@ -55,7 +55,8 @@ public enum DatabaseType Sqlite, DB2, SapHana, - Odbc + Odbc, + OleDb } /// @@ -194,6 +195,7 @@ public static class ConnectionStringBuilder DatabaseType.DB2 => BuildDb2ConnectionString(credential), DatabaseType.SapHana => BuildSapHanaConnectionString(credential), DatabaseType.Odbc => BuildOdbcConnectionString(credential), + DatabaseType.OleDb => BuildOleDbConnectionString(credential), _ => throw new NotSupportedException($"Database type {credential.DatabaseType} not supported") }; } private static string BuildSqlServerConnectionString(DatabaseCredential credential) @@ -427,6 +429,47 @@ public static class ConnectionStringBuilder return string.Join(";", builder); } + private static string BuildOleDbConnectionString(DatabaseCredential credential) + { + // Se è già presente una connection string personalizzata, utilizzala + if (!string.IsNullOrEmpty(credential.ConnectionString)) + return credential.ConnectionString; + + var builder = new List(); + + // Provider OLE DB (obbligatorio) + var provider = credential.AdditionalParameters?.GetValueOrDefault("Provider") ?? "VFPOLEDB.1"; + builder.Add($"Provider={provider}"); + + // Data Source: per VFP e Access è il percorso file/cartella + // DatabaseName è il campo principale (come per SQLite) + var dataSource = !string.IsNullOrEmpty(credential.DatabaseName) + ? credential.DatabaseName + : credential.Host; + + if (!string.IsNullOrEmpty(dataSource)) + builder.Add($"Data Source={dataSource}"); + + // Credenziali (opzionali per VFP file-based) + if (!string.IsNullOrEmpty(credential.Username)) + builder.Add($"User ID={credential.Username}"); + + if (!string.IsNullOrEmpty(credential.Password)) + builder.Add($"Password={credential.Password}"); + + // Parametri aggiuntivi specifici (es. Collating Sequence, Exclusive, DELETED per VFP) + if (credential.AdditionalParameters != null) + { + foreach (var param in credential.AdditionalParameters) + { + if (param.Key != "Provider") // Provider già gestito sopra + builder.Add($"{param.Key}={param.Value}"); + } + } + + return string.Join(";", builder); + } + private static void AddAdditionalParameters(List builder, Dictionary? additionalParams) { if (additionalParams != null) diff --git a/CredentialManager/Services/OleDbProviderDiscoveryService.cs b/CredentialManager/Services/OleDbProviderDiscoveryService.cs new file mode 100644 index 0000000..7c81a84 --- /dev/null +++ b/CredentialManager/Services/OleDbProviderDiscoveryService.cs @@ -0,0 +1,112 @@ +using Microsoft.Extensions.Logging; +using Microsoft.Win32; +using System.Runtime.InteropServices; + +namespace CredentialManager.Services; + +/// +/// Informazioni su un provider OLE DB installato nel sistema +/// +public class OleDbProviderInfo +{ + /// ProgID del provider (es. VFPOLEDB.1, Microsoft.ACE.OLEDB.12.0) + public string ProgId { get; set; } = string.Empty; + + /// Descrizione leggibile del provider + public string Description { get; set; } = string.Empty; + + /// Indica se è un provider Visual FoxPro (solo 32-bit) + public bool IsVfpProvider { get; set; } + + /// Nota aggiuntiva (es. avviso 32-bit) + public string? Note { get; set; } +} + +/// +/// Interfaccia per il servizio di discovery dei provider OLE DB installati +/// +public interface IOleDbProviderDiscoveryService +{ + /// + /// Ottiene la lista dei provider OLE DB noti installati nel sistema + /// + List GetInstalledProviders(); + + /// + /// Verifica se almeno un provider Visual FoxPro è installato + /// + bool IsVfpProviderInstalled(); +} + +/// +/// Servizio per la discovery dei provider OLE DB installati tramite il registro di Windows. +/// Controlla un elenco di provider noti verificando la presenza della chiave HKEY_CLASSES_ROOT\{ProgId}. +/// +public class OleDbProviderDiscoveryService : IOleDbProviderDiscoveryService +{ + private readonly ILogger _logger; + + /// + /// Provider OLE DB noti: ProgID → Descrizione + /// + private static readonly (string ProgId, string Description, bool IsVfp)[] KnownProviders = + { + ("VFPOLEDB.1", "Microsoft OLE DB Provider per Visual FoxPro 8.0/9.0 (32-bit)", true), + ("VFPOLEDB", "Microsoft OLE DB Provider per Visual FoxPro (32-bit)", true), + ("Microsoft.ACE.OLEDB.12.0", "Microsoft Access Database Engine 2010", false), + ("Microsoft.ACE.OLEDB.16.0", "Microsoft Access Database Engine 2016", false), + ("Microsoft.Jet.OLEDB.4.0", "Microsoft Jet 4.0 OLE DB Provider (Access/Excel 97-2003)", false), + ("SQLOLEDB", "Microsoft OLE DB Provider for SQL Server (legacy)", false), + ("SQLNCLI11", "SQL Server Native Client 11.0", false), + ("MSOLEDBSQL", "Microsoft OLE DB Driver for SQL Server", false), + ("MSDAORA", "Microsoft OLE DB Provider for Oracle (legacy)", false), + }; + + public OleDbProviderDiscoveryService(ILogger logger) + { + _logger = logger; + } + + public List GetInstalledProviders() + { + var installed = new List(); + + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + _logger.LogWarning("OLE DB è supportato solo su Windows. Nessun provider restituito."); + return installed; + } + + foreach (var (progId, description, isVfp) in KnownProviders) + { + try + { + using var key = Registry.ClassesRoot.OpenSubKey(progId); + if (key != null) + { + var note = isVfp ? "⚠ Solo 32-bit — pubblicare con --runtime win-x86" : null; + installed.Add(new OleDbProviderInfo + { + ProgId = progId, + Description = description, + IsVfpProvider = isVfp, + Note = note + }); + _logger.LogDebug("Provider OLE DB trovato: {ProgId}", progId); + } + } + catch (Exception ex) + { + _logger.LogDebug("Errore nel verificare il provider {ProgId}: {Message}", progId, ex.Message); + } + } + + _logger.LogInformation("Provider OLE DB installati trovati: {Count}", installed.Count); + return installed; + } + + public bool IsVfpProviderInstalled() + { + return GetInstalledProviders().Any(p => p.IsVfpProvider); + } +} diff --git a/DataConnection/CredentialManagement/Models/CredentialExtensions.cs b/DataConnection/CredentialManagement/Models/CredentialExtensions.cs index 3ae84ea..ea6028e 100644 --- a/DataConnection/CredentialManagement/Models/CredentialExtensions.cs +++ b/DataConnection/CredentialManagement/Models/CredentialExtensions.cs @@ -22,6 +22,7 @@ public static class CredentialExtensions CredentialManager.Models.DatabaseType.DB2 => DataConnection.Enums.DatabaseType.DB2, CredentialManager.Models.DatabaseType.SapHana => DataConnection.Enums.DatabaseType.SapHana, CredentialManager.Models.DatabaseType.Odbc => DataConnection.Enums.DatabaseType.Odbc, + CredentialManager.Models.DatabaseType.OleDb => DataConnection.Enums.DatabaseType.OleDb, _ => throw new NotSupportedException($"Database type {credentialDbType} not supported") }; } @@ -41,6 +42,7 @@ public static class CredentialExtensions DataConnection.Enums.DatabaseType.DB2 => CredentialManager.Models.DatabaseType.DB2, DataConnection.Enums.DatabaseType.SapHana => CredentialManager.Models.DatabaseType.SapHana, DataConnection.Enums.DatabaseType.Odbc => CredentialManager.Models.DatabaseType.Odbc, + DataConnection.Enums.DatabaseType.OleDb => CredentialManager.Models.DatabaseType.OleDb, _ => throw new NotSupportedException($"Database type {dataConnectionDbType} not supported") }; } diff --git a/DataConnection/CredentialManagement/Services/DataConnectionCredentialService.cs b/DataConnection/CredentialManagement/Services/DataConnectionCredentialService.cs index 03c0be0..e1ff1cc 100644 --- a/DataConnection/CredentialManagement/Services/DataConnectionCredentialService.cs +++ b/DataConnection/CredentialManagement/Services/DataConnectionCredentialService.cs @@ -251,6 +251,7 @@ public class DataConnectionCredentialService : IDataConnectionCredentialService CredentialManager.Models.DatabaseType.Oracle => await TestOracleConnection(connectionString, credential), CredentialManager.Models.DatabaseType.Sqlite => await TestSqliteConnection(connectionString, credential), CredentialManager.Models.DatabaseType.Odbc => await TestOdbcConnection(connectionString, credential), + CredentialManager.Models.DatabaseType.OleDb => await TestOleDbConnection(connectionString, credential), _ => (false, $"Test di connessione non implementato per {credential.DatabaseType}") }; } @@ -404,6 +405,46 @@ public class DataConnectionCredentialService : IDataConnectionCredentialService } } + private async Task<(bool Success, string Message)> TestOleDbConnection(string connectionString, DatabaseCredential credential) + { + try + { + using var connection = new System.Data.OleDb.OleDbConnection(connectionString); + await Task.Run(() => connection.Open()); + + var details = new System.Text.StringBuilder(); + details.AppendLine("Connessione OLE DB stabilita con successo!"); + details.AppendLine(); + details.AppendLine("Dettagli:"); + details.AppendLine($"- Provider: {connection.Provider}"); + if (!string.IsNullOrEmpty(connection.Database)) + details.AppendLine($"- Database: {connection.Database}"); + if (!string.IsNullOrEmpty(credential.DatabaseName)) + details.AppendLine($"- Data Source: {credential.DatabaseName}"); + details.AppendLine($"- Timeout: {credential.CommandTimeout}s"); + + return (true, details.ToString()); + } + catch (System.Data.OleDb.OleDbException oleDbEx) + { + var errorDetails = new System.Text.StringBuilder(); + errorDetails.AppendLine($"Errore OLE DB: {oleDbEx.Message}"); + errorDetails.AppendLine(); + errorDetails.AppendLine("Dettagli errori:"); + foreach (System.Data.OleDb.OleDbError error in oleDbEx.Errors) + { + errorDetails.AppendLine($"- [{error.SQLState}] {error.Message}"); + errorDetails.AppendLine($" Source: {error.Source}"); + } + + return (false, errorDetails.ToString()); + } + catch (Exception ex) + { + return (false, $"Errore OLE DB: {ex.Message}"); + } + } + public async Task<(bool Success, string Message)> TestRestApiConnectionAsync(string credentialName) { try diff --git a/DataConnection/DB/EF/SchemaProviders/OleDbSchemaProvider.cs b/DataConnection/DB/EF/SchemaProviders/OleDbSchemaProvider.cs new file mode 100644 index 0000000..b3ac206 --- /dev/null +++ b/DataConnection/DB/EF/SchemaProviders/OleDbSchemaProvider.cs @@ -0,0 +1,335 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Data.OleDb; +using System.Linq; +using System.Runtime.InteropServices; +using System.Threading.Tasks; +using DataConnection.Interfaces; + +namespace DataConnection.EF.SchemaProviders; + +/// +/// Provider di schema per database OLE DB (incluso Visual FoxPro) +/// Utilizza GetOleDbSchemaTable per ottenere metadati in modo compatibile con VFP e altri provider OLE DB +/// +public class OleDbSchemaProvider : IDatabaseSchemaProvider +{ + public async Task>> GetDatabaseSchemaAsync(string connectionString) + { + var result = new Dictionary>(); + + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + throw new PlatformNotSupportedException("OLE DB è supportato solo su Windows."); + + try + { + using var connection = new OleDbConnection(connectionString); + await Task.Run(() => connection.Open()); + + Console.WriteLine($"OLE DB Schema Provider - Provider: {connection.Provider}"); + + var tableNames = GetTableNamesFromConnection(connection); + Console.WriteLine($"Trovate {tableNames.Count} tabelle"); + + foreach (var tableName in tableNames) + { + try + { + var columns = GetTableColumnsFromConnection(connection, tableName); + if (columns.Any()) + { + result[tableName] = columns; + Console.WriteLine($"Tabella {tableName}: {columns.Count()} colonne"); + } + } + catch (Exception ex) + { + Console.WriteLine($"Errore nel leggere le colonne della tabella {tableName}: {ex.Message}"); + } + } + } + catch (Exception ex) + { + Console.WriteLine($"Errore in OleDbSchemaProvider.GetDatabaseSchemaAsync: {ex.Message}"); + throw; + } + + return result; + } + + public async Task> GetTableNamesAsync(string connectionString) + { + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + return Enumerable.Empty(); + + try + { + using var connection = new OleDbConnection(connectionString); + await Task.Run(() => connection.Open()); + return GetTableNamesFromConnection(connection); + } + catch (Exception ex) + { + Console.WriteLine($"Errore in OleDbSchemaProvider.GetTableNamesAsync: {ex.Message}"); + return Enumerable.Empty(); + } + } + + public async Task> GetTableSchemaAsync(string connectionString, string tableName) + { + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + return Enumerable.Empty(); + + try + { + using var connection = new OleDbConnection(connectionString); + await Task.Run(() => connection.Open()); + return GetTableColumnsFromConnection(connection, tableName); + } + catch (Exception ex) + { + Console.WriteLine($"Errore in OleDbSchemaProvider.GetTableSchemaAsync per {tableName}: {ex.Message}"); + return Enumerable.Empty(); + } + } + + public async Task> GetAvailableDatabasesAsync(string connectionString) + { + // OLE DB file-based (VFP, Access) non supporta listing di database multipli + try + { + using var connection = new OleDbConnection(connectionString); + await Task.Run(() => connection.Open()); + var db = connection.Database; + return string.IsNullOrEmpty(db) ? Enumerable.Empty() : new[] { db }; + } + catch + { + return Enumerable.Empty(); + } + } + + private static List GetTableNamesFromConnection(OleDbConnection connection) + { + var tableNames = new List(); + + try + { + // Usa GetOleDbSchemaTable - più compatibile con VFP rispetto a GetSchema() + var restrictions = new object?[] { null, null, null, "TABLE" }; + var tablesSchema = connection.GetOleDbSchemaTable(OleDbSchemaGuid.Tables, restrictions); + + if (tablesSchema != null) + { + foreach (DataRow row in tablesSchema.Rows) + { + var tableName = row["TABLE_NAME"]?.ToString(); + if (!string.IsNullOrEmpty(tableName)) + tableNames.Add(tableName); + } + } + } + catch (Exception ex) + { + Console.WriteLine($"GetOleDbSchemaTable Tables fallito, tentativo con GetSchema: {ex.Message}"); + + // Fallback a GetSchema per provider che non supportano GetOleDbSchemaTable + try + { + var tablesSchema = connection.GetSchema("Tables"); + tableNames = tablesSchema.AsEnumerable() + .Where(row => + { + var t = row["TABLE_TYPE"]?.ToString(); + return t == "TABLE" || t == "BASE TABLE"; + }) + .Select(row => row["TABLE_NAME"]?.ToString() ?? string.Empty) + .Where(n => !string.IsNullOrEmpty(n)) + .ToList(); + } + catch (Exception ex2) + { + Console.WriteLine($"GetSchema Tables fallito anche: {ex2.Message}"); + } + } + + return tableNames.OrderBy(t => t).ToList(); + } + + private static List GetTableColumnsFromConnection(OleDbConnection connection, string tableName) + { + var columns = new List(); + + try + { + // Ottieni primary keys + var primaryKeys = GetPrimaryKeys(connection, tableName); + + // Ottieni colonne via GetOleDbSchemaTable + var restrictions = new object?[] { null, null, tableName, null }; + var columnsSchema = connection.GetOleDbSchemaTable(OleDbSchemaGuid.Columns, restrictions); + + if (columnsSchema == null) + return columns; + + // Ordina per posizione ordinale + var rows = columnsSchema.AsEnumerable() + .OrderBy(r => r.IsNull("ORDINAL_POSITION") ? 0 : Convert.ToInt32(r["ORDINAL_POSITION"])) + .ToList(); + + foreach (DataRow row in rows) + { + var columnName = row["COLUMN_NAME"]?.ToString(); + if (string.IsNullOrEmpty(columnName)) + continue; + + // DATA_TYPE è un int (OleDbType enum value) + int oleDbTypeInt = row.IsNull("DATA_TYPE") ? 0 : Convert.ToInt32(row["DATA_TYPE"]); + var dataType = MapOleDbTypeToString(oleDbTypeInt); + + // Formato con dimensioni + var columnSize = row.IsNull("CHARACTER_MAXIMUM_LENGTH") ? 0 : Convert.ToInt32(row["CHARACTER_MAXIMUM_LENGTH"]); + var numericPrecision = row.IsNull("NUMERIC_PRECISION") ? 0 : Convert.ToInt32(row["NUMERIC_PRECISION"]); + var numericScale = row.IsNull("NUMERIC_SCALE") ? 0 : Convert.ToInt32(row["NUMERIC_SCALE"]); + + var formattedType = FormatDataType(dataType, columnSize, numericPrecision, numericScale); + + bool isNullable = true; + if (!row.IsNull("IS_NULLABLE")) + isNullable = Convert.ToBoolean(row["IS_NULLABLE"]); + + columns.Add(new DbColumnInfo + { + Name = columnName, + DataType = formattedType, + IsNullable = isNullable, + IsPrimaryKey = primaryKeys.Contains(columnName, StringComparer.OrdinalIgnoreCase) + }); + } + } + catch (Exception ex) + { + Console.WriteLine($"Errore nel recuperare le colonne per {tableName}: {ex.Message}"); + } + + return columns; + } + + private static HashSet GetPrimaryKeys(OleDbConnection connection, string tableName) + { + var primaryKeys = new HashSet(StringComparer.OrdinalIgnoreCase); + + try + { + var restrictions = new object?[] { null, null, tableName }; + var pkSchema = connection.GetOleDbSchemaTable(OleDbSchemaGuid.Primary_Keys, restrictions); + + if (pkSchema != null) + { + foreach (DataRow row in pkSchema.Rows) + { + var col = row["COLUMN_NAME"]?.ToString(); + if (!string.IsNullOrEmpty(col)) + primaryKeys.Add(col); + } + } + } + catch (Exception ex) + { + Console.WriteLine($"Primary keys non disponibili per {tableName}: {ex.Message}"); + + // Fallback: prova con Indexes + try + { + var restrictions = new object?[] { null, null, null, null, tableName }; + var idxSchema = connection.GetOleDbSchemaTable(OleDbSchemaGuid.Indexes, restrictions); + + if (idxSchema != null) + { + foreach (DataRow row in idxSchema.Rows) + { + if (row["PRIMARY_KEY"] is bool pk && pk) + { + var col = row["COLUMN_NAME"]?.ToString(); + if (!string.IsNullOrEmpty(col)) + primaryKeys.Add(col); + } + } + } + } + catch { /* Se anche questo fallisce, ignora */ } + } + + return primaryKeys; + } + + private static string MapOleDbTypeToString(int oleDbTypeInt) + { + // Mappa OleDbType enum (int) a nome leggibile + // https://learn.microsoft.com/en-us/dotnet/api/system.data.oledb.oledbtype + return oleDbTypeInt switch + { + 2 => "SmallInt", + 3 => "Integer", + 4 => "Single", + 5 => "Double", + 6 => "Currency", + 7 => "Date", + 8 => "VarChar", // BSTR + 9 => "IDispatch", + 10 => "Error", + 11 => "Boolean", + 12 => "Variant", + 13 => "IUnknown", + 14 => "Decimal", + 16 => "TinyInt", + 17 => "UnsignedTinyInt", + 18 => "UnsignedSmallInt", + 19 => "UnsignedInt", + 20 => "BigInt", + 21 => "UnsignedBigInt", + 64 => "DateTime", + 65 => "FileTime", + 72 => "Guid", + 128 => "Binary", + 129 => "Char", + 130 => "NVarChar", // WChar + 131 => "Decimal", // Numeric + 132 => "UserDefined", + 133 => "Date", + 134 => "Time", + 135 => "DateTime", // DBTimeStamp + 136 => "Variant", // Chapter + 138 => "PropVariant", + 139 => "VarNumeric", + 200 => "VarChar", + 201 => "LongVarChar", + 202 => "NVarChar", // VarWChar + 203 => "NText", // LongVarWChar + 204 => "VarBinary", + 205 => "Image", // LongVarBinary + _ => $"Type({oleDbTypeInt})" + }; + } + + private static string FormatDataType(string dataType, int columnSize, int numericPrecision, int numericScale) + { + var upper = dataType.ToUpperInvariant(); + + if (upper.Contains("DECIMAL") || upper.Contains("NUMERIC")) + { + if (numericPrecision > 0) + return $"{dataType}({numericPrecision},{numericScale})"; + } + else if (upper.Contains("CHAR") || upper.Contains("VARCHAR") || upper.Contains("TEXT") || upper.Contains("BINARY")) + { + if (columnSize > 0 && columnSize < 8000) + return $"{dataType}({columnSize})"; + else if (columnSize >= 8000) + return $"{dataType}(MAX)"; + } + + return dataType; + } +} diff --git a/DataConnection/DB/Enums/DatabaseType.cs b/DataConnection/DB/Enums/DatabaseType.cs index a3c453f..6bdb81d 100644 --- a/DataConnection/DB/Enums/DatabaseType.cs +++ b/DataConnection/DB/Enums/DatabaseType.cs @@ -12,5 +12,6 @@ public enum DatabaseType Sqlite, DB2, SapHana, - Odbc + Odbc, + OleDb } diff --git a/DataConnection/DB/OdbcDatabaseManager.cs b/DataConnection/DB/OdbcDatabaseManager.cs index b5e2e62..86cecb9 100644 --- a/DataConnection/DB/OdbcDatabaseManager.cs +++ b/DataConnection/DB/OdbcDatabaseManager.cs @@ -66,11 +66,19 @@ public class OdbcDatabaseManager : IDatabaseManager using var connection = new OdbcConnection(_connectionString); await connection.OpenAsync(); - // Cambia database se specificato + // Cambia database se specificato (alcuni driver come VFP non supportano ChangeDatabaseAsync) if (!string.IsNullOrEmpty(databaseName) && databaseName != _currentDatabase) { - await connection.ChangeDatabaseAsync(databaseName); - _currentDatabase = databaseName; + try + { + await connection.ChangeDatabaseAsync(databaseName); + _currentDatabase = databaseName; + } + catch (Exception dbChangeEx) + { + Console.WriteLine($"[ODBC] ChangeDatabaseAsync non supportato dal driver ({databaseName}): {dbChangeEx.Message}"); + // Continua senza cambiare database (es. driver file-based come VFP) + } } using var command = new OdbcCommand(sql, connection); diff --git a/DataConnection/DB/OleDbDatabaseManager.cs b/DataConnection/DB/OleDbDatabaseManager.cs new file mode 100644 index 0000000..7150240 --- /dev/null +++ b/DataConnection/DB/OleDbDatabaseManager.cs @@ -0,0 +1,379 @@ +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; + +/// +/// 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 +/// +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 TestConnectionAsync() + { + try + { + using var connection = new OleDbConnection(_connectionString); + await Task.Run(() => connection.Open()); + return true; + } + catch + { + return false; + } + } + + public Task> GetAsync( + Expression>? filter = null, + Func, IOrderedQueryable>? orderBy = null, + string includeProperties = "", + int? skip = null, + int? take = null) where T : class + { + throw new NotSupportedException("GetAsync con espressioni LINQ non è supportato per OLE DB. Usare ExecuteRawQueryAsync."); + } + + public Task GetByIdAsync(object id) where T : class + { + throw new NotSupportedException("GetByIdAsync non è supportato per OLE DB. Usare ExecuteRawQueryAsync con clausola WHERE."); + } + + public Task> ExecuteQueryAsync(string sql, params object[] parameters) where T : class + { + throw new NotSupportedException("ExecuteQueryAsync non è supportato per OLE DB. Usare ExecuteRawQueryAsync."); + } + + public async Task>> ExecuteRawQueryAsync(string sql, string databaseName = "", params object[] parameters) + { + var results = new List>(); + + 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(); + 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 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> 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>> GetDatabaseSchemaAsync() + { + return await _schemaProvider.GetDatabaseSchemaAsync(_connectionString); + } + + public async Task> GetTableNamesAsync() + { + return await _schemaProvider.GetTableNamesAsync(_connectionString); + } + + public async Task> GetTableSchemaAsync(string tableName) + { + return await _schemaProvider.GetTableSchemaAsync(_connectionString, tableName); + } + + public async Task>> GetAllRecordsAsync(string tableName) + { + var query = $"SELECT * FROM {tableName}"; + return await ExecuteRawQueryAsync(query); + } + + public async Task 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>> ExecuteQueryAsync(string query, int? maxRows = null) + { + var results = new List>(); + + 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(); + 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 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 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 InsertAsync(string tableName, IDictionary 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 UpdateAsync(string tableName, IDictionary data, IDictionary 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 DeleteAsync(string tableName, IDictionary 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 BulkInsertAsync(string tableName, IEnumerable> 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 UpsertRecordAsync(string tableName, string keyField, object? keyValue, Dictionary 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; + } + } + + /// + /// VFP e la maggior parte dei provider OLE DB supportano SELECT TOP N (SQL Server style) + /// + 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 + } +} diff --git a/DataConnection/DataConnection.csproj b/DataConnection/DataConnection.csproj index 20d0a33..a82e5af 100644 --- a/DataConnection/DataConnection.csproj +++ b/DataConnection/DataConnection.csproj @@ -17,6 +17,7 @@ + diff --git a/Data_Coupler/Pages/CredentialManagement.razor b/Data_Coupler/Pages/CredentialManagement.razor index a1e362e..60c1115 100644 --- a/Data_Coupler/Pages/CredentialManagement.razor +++ b/Data_Coupler/Pages/CredentialManagement.razor @@ -8,6 +8,7 @@ @using Microsoft.JSInterop @inject IDataConnectionCredentialService CredentialService @inject IOdbcDsnDiscoveryService OdbcDsnDiscoveryService +@inject IOleDbProviderDiscoveryService OleDbProviderDiscoveryService @inject IJSRuntime JSRuntime @inject NavigationManager Navigation @@ -242,6 +243,7 @@ else *@ + @@ -465,6 +467,148 @@ else } + else if (currentDatabaseCredential.DatabaseType == CredentialManager.Models.DatabaseType.OleDb) + { + +
+
+
Configurazione OLE DB
+
+
+
+ Attenzione — Compatibilità 32-bit: + Driver come VFPOLEDB.1 (Visual FoxPro) sono esclusivamente 32-bit. + Pubblica l'applicazione con dotnet publish --runtime win-x86. + Vedi PUBLISH_32BIT_64BIT.md per tutti i dettagli. +
+ +
+ + @if (availableOleDbProviders.Any()) + { + + @if (!string.IsNullOrEmpty(selectedOleDbProvider)) + { + var info = availableOleDbProviders.FirstOrDefault(p => p.ProgId == selectedOleDbProvider); + if (info?.Note != null) + { +
+ @info.Note +
+ } + } + } + else + { +
+ Nessun provider OLE DB rilevato automaticamente. + Potrebbe essere necessario eseguire in modalità 32-bit. Inserisci manualmente il ProgID: +
+ } + + + Provider comuni: VFPOLEDB.1 (VFP), Microsoft.ACE.OLEDB.12.0 (Access/Excel), Microsoft.Jet.OLEDB.4.0 (Access 97-2003) + +
+ + @if (IsVfpProvider()) + { + +
+ + + + Per database container (.dbc): percorso completo al file.
+ Per tabelle free (.dbf): percorso della cartella contenente i file. +
+
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+
+ + +
+
+
+ } + else + { +
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+ } + +
+ + + Anteprima della connection string che verrà generata +
+
+
+ } else { @@ -885,6 +1029,12 @@ else private string selectedOdbcDriver = string.Empty; private bool loadingOdbcData = false; + // OLE DB specific state + private List availableOleDbProviders = new(); + private string selectedOleDbProvider = string.Empty; + private string oleDbCollatingSequence = string.Empty; + private string oleDbDeleted = string.Empty; + protected override async Task OnInitializedAsync() { await RefreshCredentials(); CheckForProblematicCredentials(); @@ -931,6 +1081,11 @@ else { await LoadOdbcData(); } + // Se è OLE DB, carica i provider + if (currentDatabaseCredential.DatabaseType == DatabaseType.OleDb) + { + LoadOleDbData(); + } } private async Task EditDatabaseCredential(DatabaseCredential credential) @@ -963,6 +1118,19 @@ else selectedOdbcDriver = currentDatabaseCredential.AdditionalParameters["Driver"]; } } + // Se è OLE DB, carica i provider e ripristina il provider selezionato + if (currentDatabaseCredential.DatabaseType == DatabaseType.OleDb) + { + LoadOleDbData(); + if (currentDatabaseCredential.AdditionalParameters?.ContainsKey("Provider") == true) + { + selectedOleDbProvider = currentDatabaseCredential.AdditionalParameters["Provider"]; + } + if (currentDatabaseCredential.AdditionalParameters?.ContainsKey("Collating Sequence") == true) + oleDbCollatingSequence = currentDatabaseCredential.AdditionalParameters["Collating Sequence"]; + if (currentDatabaseCredential.AdditionalParameters?.ContainsKey("DELETED") == true) + oleDbDeleted = currentDatabaseCredential.AdditionalParameters["DELETED"]; + } showDatabaseModal = true; } @@ -971,6 +1139,22 @@ else { try { + // Sincronizza i parametri OLE DB negli AdditionalParameters prima del salvataggio + if (currentDatabaseCredential.DatabaseType == DatabaseType.OleDb) + { + currentDatabaseCredential.AdditionalParameters ??= new Dictionary(); + if (!string.IsNullOrEmpty(selectedOleDbProvider)) + currentDatabaseCredential.AdditionalParameters["Provider"] = selectedOleDbProvider; + if (!string.IsNullOrEmpty(oleDbCollatingSequence)) + currentDatabaseCredential.AdditionalParameters["Collating Sequence"] = oleDbCollatingSequence; + else + currentDatabaseCredential.AdditionalParameters.Remove("Collating Sequence"); + if (!string.IsNullOrEmpty(oleDbDeleted)) + currentDatabaseCredential.AdditionalParameters["DELETED"] = oleDbDeleted; + else + currentDatabaseCredential.AdditionalParameters.Remove("DELETED"); + } + await CredentialService.SaveDatabaseCredentialAsync(currentDatabaseCredential); await JSRuntime.InvokeVoidAsync("alert", "Credenziale database salvata con successo!"); CloseDatabaseModal(); @@ -1289,6 +1473,64 @@ else StateHasChanged(); } + // OLE DB Methods + + private void LoadOleDbData() + { + try + { + availableOleDbProviders = OleDbProviderDiscoveryService.GetInstalledProviders(); + } + catch + { + availableOleDbProviders = new List(); + } + } + + private void RefreshOleDbProviderList() + { + LoadOleDbData(); + StateHasChanged(); + } + + private bool IsVfpProvider() + { + if (string.IsNullOrEmpty(selectedOleDbProvider)) + return false; + return selectedOleDbProvider.StartsWith("VFPOLEDB", StringComparison.OrdinalIgnoreCase); + } + + private string GetOleDbConnectionStringPreview() + { + if (currentDatabaseCredential.DatabaseType != DatabaseType.OleDb) + return string.Empty; + + try + { + // Copia i parametri OLE DB nei AdditionalParameters temporaneamente + currentDatabaseCredential.AdditionalParameters ??= new Dictionary(); + + if (!string.IsNullOrEmpty(selectedOleDbProvider)) + currentDatabaseCredential.AdditionalParameters["Provider"] = selectedOleDbProvider; + + if (!string.IsNullOrEmpty(oleDbCollatingSequence)) + currentDatabaseCredential.AdditionalParameters["Collating Sequence"] = oleDbCollatingSequence; + else + currentDatabaseCredential.AdditionalParameters.Remove("Collating Sequence"); + + if (!string.IsNullOrEmpty(oleDbDeleted)) + currentDatabaseCredential.AdditionalParameters["DELETED"] = oleDbDeleted; + else + currentDatabaseCredential.AdditionalParameters.Remove("DELETED"); + + return ConnectionStringBuilder.BuildConnectionString(currentDatabaseCredential); + } + catch (Exception ex) + { + return $"Errore nella generazione: {ex.Message}"; + } + } + #endregion #endregion diff --git a/Data_Coupler/Program.cs b/Data_Coupler/Program.cs index 33fe871..20b1bf2 100644 --- a/Data_Coupler/Program.cs +++ b/Data_Coupler/Program.cs @@ -109,6 +109,7 @@ builder.Services.AddScoped(); // Register ODBC DSN Discovery Service builder.Services.AddScoped(); +builder.Services.AddScoped(); // Register Association Service (Pre-Discovery) builder.Services.AddScoped(); diff --git a/Data_Coupler/Services/DataConnectionFactory.cs b/Data_Coupler/Services/DataConnectionFactory.cs index 4139870..4926405 100644 --- a/Data_Coupler/Services/DataConnectionFactory.cs +++ b/Data_Coupler/Services/DataConnectionFactory.cs @@ -83,6 +83,14 @@ namespace Data_Coupler.Services return new DataConnection.DB.OdbcDatabaseManager(connectionString); } + // Per OLE DB, usa OleDbDatabaseManager direttamente (EF Core non supporta OLE DB) + if (credential.DatabaseType == DatabaseType.OleDb) + { + var connectionString = CredentialManager.Models.ConnectionStringBuilder.BuildConnectionString(credential); + _logger.LogInformation("Creando OleDbDatabaseManager con connection string per {CredentialName}", credentialName); + return new DataConnection.DB.OleDbDatabaseManager(connectionString); + } + // Per altri database, usa EFCoreDatabaseManager var dbManagerOptions = await _credentialService.GetDbManagerOptionsAsync(credential.Name); return new EFCoreDatabaseManager(dbManagerOptions); diff --git a/PUBLISH_32BIT_64BIT.md b/PUBLISH_32BIT_64BIT.md new file mode 100644 index 0000000..ee4964f --- /dev/null +++ b/PUBLISH_32BIT_64BIT.md @@ -0,0 +1,164 @@ +# Pubblicazione Data-Coupler: Guida 32-bit e 64-bit + +## Perché è importante scegliere la piattaforma target + +Alcune tecnologie di connessione sono vincolate alla piattaforma (32-bit o 64-bit): + +| Tecnologia | Supporto | Note | +|---|---|---| +| **VFPOLEDB.1** (Visual FoxPro) | **Solo 32-bit** | Driver COM 32-bit, incompatibile con processi 64-bit | +| **Microsoft.ACE.OLEDB.12.0** (Access 2010) | 32-bit o 64-bit (match) | Installa la versione corrispondente all'app | +| **Microsoft.Jet.OLEDB.4.0** | **Solo 32-bit** | Driver legacy | +| ODBC generico | Dipende dal driver | Usa Gestore ODBC a 64-bit per driver 64-bit | +| SQL Server, MySQL, PostgreSQL, ecc. | 64-bit (consigliato) | Driver nativi .NET, nessun vincolo | + +--- + +## Comandi di Pubblicazione + +### 1. Pubblicazione Windows 64-bit (default, consigliato per SQL Server/MySQL/API REST) + +```powershell +dotnet publish Data_Coupler/Data_Coupler.csproj ` + --configuration Release ` + --runtime win-x64 ` + --self-contained true ` + --output ./publish/win-x64 +``` + +**Usa per**: SQL Server, MySQL, PostgreSQL, Oracle, REST API, ODBC 64-bit +**Non usare per**: VFPOLEDB, Jet 4.0 + +--- + +### 2. Pubblicazione Windows 32-bit (richiesta per Visual FoxPro / VFPOLEDB) + +```powershell +dotnet publish Data_Coupler/Data_Coupler.csproj ` + --configuration Release ` + --runtime win-x86 ` + --self-contained true ` + --output ./publish/win-x86 +``` + +**Usa per**: VFPOLEDB.1 (Visual FoxPro 8/9), Microsoft.Jet.OLEDB.4.0, driver OLE DB legacy 32-bit +**Nota**: Il processo sarà 32-bit — massima RAM ≈ 4GB. + +--- + +### 3. Pubblicazione Linux x64 + +```powershell +dotnet publish Data_Coupler/Data_Coupler.csproj ` + --configuration Release ` + --runtime linux-x64 ` + --self-contained true ` + --output ./publish/linux-x64 +``` + +**Attenzione**: OLE DB e ODBC (drivers Windows) **non sono supportati** su Linux. +Su Linux sono disponibili solo: SQL Server, MySQL, PostgreSQL, Oracle, SQLite, DB2, SAP HANA, REST API. + +--- + +### 4. Pubblicazione macOS (Intel) + +```powershell +dotnet publish Data_Coupler/Data_Coupler.csproj ` + --configuration Release ` + --runtime osx-x64 ` + --self-contained true ` + --output ./publish/osx-x64 +``` + +--- + +### 5. Pubblicazione macOS (Apple Silicon - ARM64) + +```powershell +dotnet publish Data_Coupler/Data_Coupler.csproj ` + --configuration Release ` + --runtime osx-arm64 ` + --self-contained true ` + --output ./publish/osx-arm64 +``` + +--- + +## Pubblicazione Framework-Dependent (senza runtime incluso) + +Se .NET 9 è già installato sul server target: + +```powershell +# Windows (framework-dependent, lascia a .NET la scelta della bitness) +dotnet publish Data_Coupler/Data_Coupler.csproj ` + --configuration Release ` + --output ./publish/framework-dependent + +# Forzare 32-bit anche in framework-dependent (per VFP): +# Compilare il progetto con PlatformTarget = x86 o usare --runtime win-x86 +``` + +--- + +## Setup per Visual FoxPro (VFPOLEDB.1) + +### Prerequisiti + +1. **Driver VFPOLEDB installato** (32-bit): + - Download: [Microsoft OLE DB Provider for Visual FoxPro 9.0 SP2](https://www.microsoft.com/en-us/download/details.aspx?id=14839) + - Verifica installazione: `HKEY_CLASSES_ROOT\VFPOLEDB.1` deve esistere nel registro + +2. **Applicazione pubblicata come 32-bit** (`--runtime win-x86`) + +3. **Connection string esempio VFP**: + ``` + Provider=VFPOLEDB.1;Data Source=C:\VFP\Database\miodb.dbc;Collating Sequence=machine; + ``` + Per tabelle free (.dbf): + ``` + Provider=VFPOLEDB.1;Data Source=C:\VFP\Tabelle\;Collating Sequence=machine; + ``` + +### Verifica Rapida in PowerShell + +```powershell +# Verifica driver VFP installato +Get-Item "HKCR:\VFPOLEDB.1" -ErrorAction SilentlyContinue + +# Verifica processo 32-bit in esecuzione +[System.Environment]::Is64BitProcess # deve restituire False +``` + +--- + +## Docker + +Per Docker con VFP (non consigliato — driver COM Windows-only): + +```dockerfile +# Nel Dockerfile, non è possibile usare VFPOLEDB su Linux container +# Usare Windows Container (opzione Dockerfile.windows) +``` + +Per **Windows Container** con supporto 32-bit, modifica il `Dockerfile.windows`: + +```dockerfile +# Usa immagine Windows nano server +FROM mcr.microsoft.com/windows/nanoserver:ltsc2022 + +# Copia publish win-x86 +COPY ./publish/win-x86 /app +``` + +--- + +## Riepilogo Rapido + +| Scenario | Comando | +|---|---| +| Solo database SQL + REST API | `--runtime win-x64` | +| Visual FoxPro / OLE DB legacy | `--runtime win-x86` | +| Server Linux | `--runtime linux-x64` | +| macOS Intel | `--runtime osx-x64` | +| macOS Apple Silicon | `--runtime osx-arm64` |