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; } }