using System; using System.Collections.Generic; using System.Data; using System.Data.Odbc; using System.Linq; using System.Threading.Tasks; using DataConnection.Interfaces; namespace DataConnection.EF.SchemaProviders; /// /// Provider di schema per database ODBC generici /// Utilizza le funzioni ODBC standard per ottenere metadati del database /// public class OdbcSchemaProvider : IDatabaseSchemaProvider { public async Task>> GetDatabaseSchemaAsync(string connectionString) { var result = new Dictionary>(); try { using var connection = new OdbcConnection(connectionString); await connection.OpenAsync(); Console.WriteLine($"ODBC Schema Provider - Connesso a: {connection.Database}"); Console.WriteLine($"Driver: {connection.Driver}"); Console.WriteLine($"Server Version: {connection.ServerVersion}"); // Ottieni le tabelle dal database usando GetSchema var tablesSchema = connection.GetSchema("Tables"); // Filtra solo le tabelle utente (esclude views, system tables, ecc.) var userTables = tablesSchema.AsEnumerable() .Where(row => { var tableType = row["TABLE_TYPE"].ToString(); return tableType == "TABLE" || tableType == "BASE TABLE"; }) .Select(row => new { Schema = row.IsNull("TABLE_SCHEM") ? null : row["TABLE_SCHEM"].ToString(), TableName = row["TABLE_NAME"].ToString() ?? string.Empty, FullName = GetFullTableName(row) }) .Where(t => !string.IsNullOrEmpty(t.TableName)) .ToList(); Console.WriteLine($"Trovate {userTables.Count} tabelle utente"); // Per ogni tabella, ottieni le colonne foreach (var table in userTables) { try { var columns = await GetTableColumnsAsync(connection, table.Schema, table.TableName); if (columns.Any()) { result[table.FullName] = columns; Console.WriteLine($"Tabella {table.FullName}: {columns.Count()} colonne"); } } catch (Exception ex) { Console.WriteLine($"Errore nel leggere le colonne della tabella {table.FullName}: {ex.Message}"); } } if (result.Count == 0) { Console.WriteLine("ATTENZIONE: Nessuna tabella trovata o nessuna colonna leggibile"); } } catch (Exception ex) { Console.WriteLine($"Errore in OdbcSchemaProvider.GetDatabaseSchemaAsync: {ex.Message}"); throw; } return result; } private static string GetFullTableName(DataRow tableRow) { var schema = tableRow.IsNull("TABLE_SCHEM") ? null : tableRow["TABLE_SCHEM"].ToString(); var tableName = tableRow["TABLE_NAME"].ToString() ?? string.Empty; if (!string.IsNullOrEmpty(schema) && schema != "dbo") return $"{schema}.{tableName}"; return tableName; } private async Task> GetTableColumnsAsync(OdbcConnection connection, string? schemaName, string tableName) { var columns = new List(); try { // Usa GetSchema per ottenere le colonne // Alcuni driver ODBC supportano restrizioni per schema e table name string?[] restrictions = new string?[4]; restrictions[0] = null; // Catalog restrictions[1] = schemaName; // Schema restrictions[2] = tableName; // Table name restrictions[3] = null; // Column name DataTable columnsSchema; try { columnsSchema = connection.GetSchema("Columns", restrictions); } catch { // Alcuni driver non supportano le restrizioni, proviamo senza columnsSchema = connection.GetSchema("Columns"); // Filtra manualmente per table name columnsSchema = columnsSchema.AsEnumerable() .Where(row => row["TABLE_NAME"].ToString() == tableName) .CopyToDataTable(); } // Ottieni le primary keys per questa tabella var primaryKeys = GetPrimaryKeys(connection, schemaName, tableName); // Ottieni le foreign keys per questa tabella var foreignKeys = GetForeignKeys(connection, schemaName, tableName); foreach (DataRow columnRow in columnsSchema.Rows) { var columnName = columnRow["COLUMN_NAME"].ToString() ?? string.Empty; if (string.IsNullOrEmpty(columnName)) continue; var dataType = columnRow["TYPE_NAME"].ToString() ?? "unknown"; var isNullable = ParseNullable(columnRow["IS_NULLABLE"]); // Formatta il tipo di dati con dimensioni se disponibili var formattedDataType = FormatDataType(dataType, columnRow); var columnInfo = new DbColumnInfo { Name = columnName, DataType = formattedDataType, IsNullable = isNullable, IsPrimaryKey = primaryKeys.Contains(columnName), IsForeignKey = foreignKeys.ContainsKey(columnName), ReferencedTable = foreignKeys.ContainsKey(columnName) ? foreignKeys[columnName].ReferencedTable : null, ReferencedColumn = foreignKeys.ContainsKey(columnName) ? foreignKeys[columnName].ReferencedColumn : null }; columns.Add(columnInfo); } } catch (Exception ex) { Console.WriteLine($"Errore nel recuperare le colonne per {tableName}: {ex.Message}"); } return columns; } private HashSet GetPrimaryKeys(OdbcConnection connection, string? schemaName, string tableName) { var primaryKeys = new HashSet(StringComparer.OrdinalIgnoreCase); try { string?[] restrictions = new string?[4]; restrictions[0] = null; // Catalog restrictions[1] = schemaName; // Schema restrictions[2] = tableName; // Table name restrictions[3] = null; // Column name var pkSchema = connection.GetSchema("PrimaryKeys", restrictions); foreach (DataRow row in pkSchema.Rows) { var columnName = row["COLUMN_NAME"].ToString(); if (!string.IsNullOrEmpty(columnName)) primaryKeys.Add(columnName); } } catch (Exception ex) { // Alcuni driver ODBC non supportano PrimaryKeys schema collection Console.WriteLine($"GetSchema PrimaryKeys non supportato: {ex.Message}"); } return primaryKeys; } private Dictionary GetForeignKeys(OdbcConnection connection, string? schemaName, string tableName) { var foreignKeys = new Dictionary(StringComparer.OrdinalIgnoreCase); try { string?[] restrictions = new string?[4]; restrictions[0] = null; // Catalog restrictions[1] = schemaName; // Schema restrictions[2] = tableName; // Table name restrictions[3] = null; // Column name var fkSchema = connection.GetSchema("ForeignKeys", restrictions); foreach (DataRow row in fkSchema.Rows) { var columnName = row["FKCOLUMN_NAME"].ToString(); var referencedTable = row["PKTABLE_NAME"].ToString(); var referencedColumn = row["PKCOLUMN_NAME"].ToString(); if (!string.IsNullOrEmpty(columnName) && !string.IsNullOrEmpty(referencedTable) && !string.IsNullOrEmpty(referencedColumn)) { foreignKeys[columnName] = (referencedTable, referencedColumn); } } } catch (Exception ex) { // Alcuni driver ODBC non supportano ForeignKeys schema collection Console.WriteLine($"GetSchema ForeignKeys non supportato: {ex.Message}"); } return foreignKeys; } private bool ParseNullable(object? isNullableValue) { if (isNullableValue == null || isNullableValue == DBNull.Value) return true; var strValue = isNullableValue.ToString()?.ToUpperInvariant(); return strValue switch { "YES" => true, "NO" => false, "1" => true, "0" => false, _ => true // Default a nullable se non riusciamo a determinarlo }; } private string FormatDataType(string dataType, DataRow columnRow) { try { // Prova ad ottenere lunghezza/precisione/scala var columnSize = columnRow.IsNull("COLUMN_SIZE") ? 0 : Convert.ToInt32(columnRow["COLUMN_SIZE"]); var decimalDigits = columnRow.IsNull("DECIMAL_DIGITS") ? 0 : Convert.ToInt32(columnRow["DECIMAL_DIGITS"]); var upperDataType = dataType.ToUpperInvariant(); // Tipi numerici con precisione e scala if (upperDataType.Contains("DECIMAL") || upperDataType.Contains("NUMERIC")) { if (columnSize > 0 && decimalDigits >= 0) return $"{dataType}({columnSize},{decimalDigits})"; } // Tipi stringa con lunghezza else if (upperDataType.Contains("CHAR") || upperDataType.Contains("VARCHAR") || upperDataType.Contains("TEXT") || upperDataType.Contains("STRING")) { if (columnSize > 0 && columnSize < 8000) return $"{dataType}({columnSize})"; else if (columnSize >= 8000) return $"{dataType}(MAX)"; } // Tipi floating point else if (upperDataType.Contains("FLOAT") || upperDataType.Contains("DOUBLE") || upperDataType.Contains("REAL")) { if (columnSize > 0) return $"{dataType}({columnSize})"; } return dataType; } catch { return dataType; } } public async Task> GetAvailableDatabasesAsync(string connectionString) { var databases = new List(); try { using var connection = new OdbcConnection(connectionString); await connection.OpenAsync(); // Tenta di ottenere i database disponibili usando GetSchema try { var catalogsSchema = connection.GetSchema("Catalogs"); foreach (DataRow row in catalogsSchema.Rows) { var catalogName = row["CATALOG_NAME"]?.ToString(); if (!string.IsNullOrEmpty(catalogName)) databases.Add(catalogName); } } catch (Exception ex) { Console.WriteLine($"GetSchema Catalogs non supportato: {ex.Message}"); // Fallback: alcuni driver potrebbero usare "Databases" invece di "Catalogs" try { var dbSchema = connection.GetSchema("Databases"); foreach (DataRow row in dbSchema.Rows) { var dbName = row[0]?.ToString(); // Prima colonna dovrebbe essere il nome if (!string.IsNullOrEmpty(dbName)) databases.Add(dbName); } } catch { // Se nemmeno questo funziona, restituisci il database corrente if (!string.IsNullOrEmpty(connection.Database)) databases.Add(connection.Database); } } } catch (Exception ex) { Console.WriteLine($"Errore in GetAvailableDatabasesAsync: {ex.Message}"); } return databases; } public async Task> GetTableNamesAsync(string connectionString) { var tableNames = new List(); try { using var connection = new OdbcConnection(connectionString); await connection.OpenAsync(); var tablesSchema = connection.GetSchema("Tables"); tableNames = tablesSchema.AsEnumerable() .Where(row => { var tableType = row["TABLE_TYPE"].ToString(); return tableType == "TABLE" || tableType == "BASE TABLE"; }) .Select(row => GetFullTableName(row)) .Where(name => !string.IsNullOrEmpty(name)) .ToList(); } catch (Exception ex) { Console.WriteLine($"Errore in GetTableNamesAsync: {ex.Message}"); } return tableNames; } public async Task> GetTableSchemaAsync(string connectionString, string tableName) { try { using var connection = new OdbcConnection(connectionString); await connection.OpenAsync(); // Separa schema e nome tabella se presente il punto string? schemaName = null; string actualTableName = tableName; if (tableName.Contains('.')) { var parts = tableName.Split('.'); schemaName = parts[0]; actualTableName = parts[1]; } return await GetTableColumnsAsync(connection, schemaName, actualTableName); } catch (Exception ex) { Console.WriteLine($"Errore in GetTableSchemaAsync per {tableName}: {ex.Message}"); return Enumerable.Empty(); } } }