82e0d6bc77
## 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)
336 lines
12 KiB
C#
336 lines
12 KiB
C#
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;
|
|
|
|
/// <summary>
|
|
/// 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
|
|
/// </summary>
|
|
public class OleDbSchemaProvider : IDatabaseSchemaProvider
|
|
{
|
|
public async Task<IDictionary<string, IEnumerable<DbColumnInfo>>> GetDatabaseSchemaAsync(string connectionString)
|
|
{
|
|
var result = new Dictionary<string, IEnumerable<DbColumnInfo>>();
|
|
|
|
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<IEnumerable<string>> GetTableNamesAsync(string connectionString)
|
|
{
|
|
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
|
return Enumerable.Empty<string>();
|
|
|
|
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<string>();
|
|
}
|
|
}
|
|
|
|
public async Task<IEnumerable<DbColumnInfo>> GetTableSchemaAsync(string connectionString, string tableName)
|
|
{
|
|
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
|
return Enumerable.Empty<DbColumnInfo>();
|
|
|
|
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<DbColumnInfo>();
|
|
}
|
|
}
|
|
|
|
public async Task<IEnumerable<string>> 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<string>() : new[] { db };
|
|
}
|
|
catch
|
|
{
|
|
return Enumerable.Empty<string>();
|
|
}
|
|
}
|
|
|
|
private static List<string> GetTableNamesFromConnection(OleDbConnection connection)
|
|
{
|
|
var tableNames = new List<string>();
|
|
|
|
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<DbColumnInfo> GetTableColumnsFromConnection(OleDbConnection connection, string tableName)
|
|
{
|
|
var columns = new List<DbColumnInfo>();
|
|
|
|
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<string> GetPrimaryKeys(OleDbConnection connection, string tableName)
|
|
{
|
|
var primaryKeys = new HashSet<string>(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;
|
|
}
|
|
}
|