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