using System.Linq.Expressions; using DataConnection.DB.FoxPro; using DataConnection.Interfaces; namespace DataConnection.DB; /// /// Database manager per sorgenti Visual FoxPro / dBase in modalità completamente /// managed: legge direttamente i file .dbf/.fpt/.dbc tramite la /// libreria DbfDataReader, senza alcun provider OLE DB/ODBC e funzionando a 64-bit. /// /// Sola lettura: FoxPro è supportato come sorgente dati. Le operazioni di /// scrittura lanciano — scrivere nei .dbf rischierebbe /// di corrompere indici .cdx e memo .fpt dei gestionali legacy. /// /// La "connection string" è semplicemente un percorso: un file .dbc (container) oppure /// una cartella di tabelle libere .dbf. Vedi . /// public class FoxProDatabaseManager : IDatabaseManager { private const string ReadOnlyMessage = "Visual FoxPro è supportato in sola lettura (sorgente dati). La scrittura sui file .dbf non è disponibile."; private readonly FoxProConnectionInfo _info; public FoxProDatabaseManager(string connectionString) { _info = FoxProReader.Resolve(connectionString); } public async Task TestConnectionAsync() { try { await Task.Run(() => { FoxProReader.EnsureReachable(_info); // Forza la lettura dell'elenco tabelle: conferma che cartella/.dbc siano leggibili. _ = FoxProReader.GetTableNames(_info); }); return true; } catch { return false; } } public Task> GetTableNamesAsync() => Task.Run(() => (IEnumerable)FoxProReader.GetTableNames(_info)); public Task> GetTableSchemaAsync(string tableName) => Task.Run(() => (IEnumerable)FoxProReader.GetTableColumns(_info, tableName)); public Task>> GetDatabaseSchemaAsync() { return Task.Run(() => { var result = new Dictionary>(StringComparer.OrdinalIgnoreCase); foreach (var table in FoxProReader.GetTableNames(_info)) { try { var columns = FoxProReader.GetTableColumns(_info, table); if (columns.Count > 0) result[table] = columns; } catch (Exception ex) { Console.WriteLine($"[FoxPro] Schema non leggibile per {table}: {ex.Message}"); } } return (IDictionary>)result; }); } public Task>> GetAllRecordsAsync(string tableName) => Task.Run(() => (IEnumerable>)FoxProReader.ReadRecords(_info, tableName).ToList()); public Task>> ExecuteRawQueryAsync(string sql, string databaseName = "", params object[] parameters) { return Task.Run(() => { var parsed = FoxProReader.ParseSimpleSelect(sql); if (parsed == null) throw new NotSupportedException( "Per le sorgenti FoxPro è supportato solo 'SELECT [TOP n] * FROM '. " + "Impossibile interpretare la query: " + sql); var (table, top) = parsed.Value; return FoxProReader.ReadRecords(_info, table, top).ToList(); }); } public Task GetPrimaryKeyFieldAsync(string tableName) // Le tabelle .dbf non espongono una PK affidabile: la chiave si seleziona nel coupler. => Task.FromResult(null); public Task> GetAvailableDatabasesAsync() => Task.FromResult(new List { _info.DisplayName }); public Task ChangeDatabaseAsync(string databaseName) { // Sorgente file-based: il database è definito dal percorso. Nessun cambio a runtime. return Task.CompletedTask; } // ===== Operazioni non supportate (sola lettura) ===== public Task ExecuteCommandAsync(string sql, params object[] parameters) => throw new NotSupportedException(ReadOnlyMessage); public Task UpsertRecordAsync(string tableName, string keyField, object? keyValue, Dictionary record) => throw new NotSupportedException(ReadOnlyMessage); // ===== Operazioni LINQ/tipizzate non applicabili a una sorgente DBF ===== 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 LINQ non è supportato per FoxPro. Usare GetAllRecordsAsync."); public Task GetByIdAsync(object id) where T : class => throw new NotSupportedException("GetByIdAsync non è supportato per FoxPro."); public Task> ExecuteQueryAsync(string sql, params object[] parameters) where T : class => throw new NotSupportedException("ExecuteQueryAsync tipizzato non è supportato per FoxPro. Usare ExecuteRawQueryAsync."); public void Dispose() { // Nessuna risorsa persistente: ogni operazione apre/chiude i file. GC.SuppressFinalize(this); } }