e70abcdcb1
Aggiunge un connettore completamente managed per database Visual FoxPro e dBase, utilizzabile come sorgente dati in tutti i flussi dell'applicazione (discovery manuale, esecuzione schedulata, hash/change-detection, backup/restore). ## Motivazione Il provider OLE DB ufficiale (VFPOLEDB.1) è solo a 32-bit, deprecato e richiede installazione separata. Il connettore managed usa DbfDataReader 1.0.0 (MIT), legge direttamente i file .dbf/.fpt/.dbc a 64-bit senza dipendenze esterne e con streaming efficiente (tabelle da centinaia di MB con uso di memoria contenuto). ## Nuovi file - DataConnection/DB/FoxPro/FoxProConnectionInfo.cs Classe sealed con le informazioni di connessione risolte (cartella, percorso .dbc, encoding, flag IncludeDeleted). Proprietà di convenienza IsContainer e DisplayName. - DataConnection/DB/FoxPro/FoxProReader.cs Helper statico che registra CodePagesEncodingProvider, risolve la connection string (path diretto, stile OLE DB con Provider=vfpoledb, chiave=valore), verifica l'accesso al filesystem, legge il catalogo .dbc (il file .dbc è esso stesso un DBF; filtro su OBJECTTYPE='Table' + cross-check esistenza fisica .dbf), enumera le tabelle free, mappa i tipi VFP (Character, Memo, Numeric, Float, Double, Integer, Currency, Date, DateTime, Logical, General) in descrizioni leggibili, esegue streaming dei record via DbfDataReader, auto-rileva l'encoding dall'header DBF (language driver byte) con fallback a code page 1252, e analizza query SELECT [TOP n] * FROM tabella. - DataConnection/DB/FoxProDatabaseManager.cs Implementa IDatabaseManager. Lettura: TestConnectionAsync, GetTableNamesAsync, GetTableSchemaAsync, GetDatabaseSchemaAsync, GetAllRecordsAsync, ExecuteRawQueryAsync, GetPrimaryKeyFieldAsync (ritorna null, selezione manuale chiave), GetAvailableDatabasesAsync, ChangeDatabaseAsync (no-op per sorgenti file-based). Scrittura: tutti i metodi di modifica (Insert/Update/Delete/Upsert/BulkInsert/ ExecuteCommand/ExecuteNonQuery) lanciano NotSupportedException con messaggio esplicito. - DataConnection/DB/EF/SchemaProviders/FoxProSchemaProvider.cs Implementa IDatabaseSchemaProvider delegando a FoxProReader. Gestione errori per tabella in GetDatabaseSchemaAsync (una tabella non apribile non blocca la discovery). - FOXPRO_CONNECTION.md Guida utente: passo-passo per creare la credenziale, formato connection string, tabella tipi VFP supportati, note su sola lettura e limitazioni query, tabella risoluzione problemi (caratteri accentati, tabelle mancanti, ecc.). ## File modificati - DataConnection/DataConnection.csproj Aggiunto DbfDataReader 1.0.0 (porta transitivamente System.Text.Encoding.CodePages >= 10.0.3; rimossa dipendenza esplicita alla versione 9.0.3 che causava NU1605). - DataConnection/DB/Enums/DatabaseType.cs Aggiunto valore Foxpro in fondo all'enum (preserva valori già persistiti). - CredentialManager/Models/CredentialModels.cs Aggiunto DatabaseType.Foxpro all'enum e metodo BuildFoxproConnectionString che genera "Data Source=percorso[;CodePage=n][;IncludeDeleted=true]". - DataConnection/CredentialManagement/Models/CredentialExtensions.cs Mappatura bidirezionale Foxpro tra enum CredentialManager e DataConnection. - DataConnection/CredentialManagement/Services/DataConnectionCredentialService.cs Aggiunto TestFoxproConnection: verifica percorso accessibile, legge nomi tabelle, restituisce messaggio con conteggio tabelle, encoding rilevato e nota sola lettura. - DataConnection/DB/EF/DatabaseSchemaProviderFactory.cs Caso Foxpro => new FoxProSchemaProvider(). - Data_Coupler/Services/DataConnectionFactory.cs Branch Foxpro => new FoxProDatabaseManager(connectionString). - Data_Coupler/Extensions/DataCoupler/DatabaseMethod.cs Aggiunto IsFoxproConnection() e caso Foxpro in CreateLimitedQuery (SELECT TOP n * FROM (query) AS subquery). - CredentialManager/Integration/DataConnectionHelper.cs ValidateDatabaseCredential: introdotto flag isFileBased (Sqlite || Foxpro) per esonerare host/utente/password/porta; messaggio di errore specifico per il campo percorso FoxPro. - Data_Coupler/Pages/CredentialManagement.razor Aggiunta opzione "Visual FoxPro (.dbc / .dbf)" nel selettore tipo database. Sezione di configurazione dedicata: banner sola lettura, campo percorso con esempi (.dbc e cartella), dropdown code page (Auto/1252/1250/1251/850/437/65001), checkbox record cancellati, pannello tipi supportati, anteprima connection string. Fix validazione form test-connessione: branch Foxpro che verifica solo DatabaseName (non Host/Username/Password), eliminando il falso errore "Il campo Host è obbligatorio". Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
138 lines
5.6 KiB
C#
138 lines
5.6 KiB
C#
using System.Linq.Expressions;
|
|
using DataConnection.DB.FoxPro;
|
|
using DataConnection.Interfaces;
|
|
|
|
namespace DataConnection.DB;
|
|
|
|
/// <summary>
|
|
/// Database manager per sorgenti <b>Visual FoxPro / dBase</b> in modalità completamente
|
|
/// <b>managed</b>: legge direttamente i file <c>.dbf</c>/<c>.fpt</c>/<c>.dbc</c> tramite la
|
|
/// libreria <c>DbfDataReader</c>, senza alcun provider OLE DB/ODBC e funzionando a 64-bit.
|
|
///
|
|
/// <para><b>Sola lettura</b>: FoxPro è supportato come <i>sorgente</i> dati. Le operazioni di
|
|
/// scrittura lanciano <see cref="NotSupportedException"/> — scrivere nei <c>.dbf</c> rischierebbe
|
|
/// di corrompere indici <c>.cdx</c> e memo <c>.fpt</c> dei gestionali legacy.</para>
|
|
///
|
|
/// La "connection string" è semplicemente un percorso: un file <c>.dbc</c> (container) oppure
|
|
/// una cartella di tabelle libere <c>.dbf</c>. Vedi <see cref="FoxProReader.Resolve"/>.
|
|
/// </summary>
|
|
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<bool> 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<IEnumerable<string>> GetTableNamesAsync()
|
|
=> Task.Run(() => (IEnumerable<string>)FoxProReader.GetTableNames(_info));
|
|
|
|
public Task<IEnumerable<DbColumnInfo>> GetTableSchemaAsync(string tableName)
|
|
=> Task.Run(() => (IEnumerable<DbColumnInfo>)FoxProReader.GetTableColumns(_info, tableName));
|
|
|
|
public Task<IDictionary<string, IEnumerable<DbColumnInfo>>> GetDatabaseSchemaAsync()
|
|
{
|
|
return Task.Run(() =>
|
|
{
|
|
var result = new Dictionary<string, IEnumerable<DbColumnInfo>>(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<string, IEnumerable<DbColumnInfo>>)result;
|
|
});
|
|
}
|
|
|
|
public Task<IEnumerable<Dictionary<string, object>>> GetAllRecordsAsync(string tableName)
|
|
=> Task.Run(() => (IEnumerable<Dictionary<string, object>>)FoxProReader.ReadRecords(_info, tableName).ToList());
|
|
|
|
public Task<List<Dictionary<string, object>>> 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 <tabella>'. " +
|
|
"Impossibile interpretare la query: " + sql);
|
|
|
|
var (table, top) = parsed.Value;
|
|
return FoxProReader.ReadRecords(_info, table, top).ToList();
|
|
});
|
|
}
|
|
|
|
public Task<string?> GetPrimaryKeyFieldAsync(string tableName)
|
|
// Le tabelle .dbf non espongono una PK affidabile: la chiave si seleziona nel coupler.
|
|
=> Task.FromResult<string?>(null);
|
|
|
|
public Task<List<string>> GetAvailableDatabasesAsync()
|
|
=> Task.FromResult(new List<string> { _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<int> ExecuteCommandAsync(string sql, params object[] parameters)
|
|
=> throw new NotSupportedException(ReadOnlyMessage);
|
|
|
|
public Task<bool> UpsertRecordAsync(string tableName, string keyField, object? keyValue, Dictionary<string, object?> record)
|
|
=> throw new NotSupportedException(ReadOnlyMessage);
|
|
|
|
// ===== Operazioni LINQ/tipizzate non applicabili a una sorgente DBF =====
|
|
|
|
public Task<IEnumerable<T>> GetAsync<T>(
|
|
Expression<Func<T, bool>>? filter = null,
|
|
Func<IQueryable<T>, IOrderedQueryable<T>>? orderBy = null,
|
|
string includeProperties = "",
|
|
int? skip = null,
|
|
int? take = null) where T : class
|
|
=> throw new NotSupportedException("GetAsync<T> con LINQ non è supportato per FoxPro. Usare GetAllRecordsAsync.");
|
|
|
|
public Task<T?> GetByIdAsync<T>(object id) where T : class
|
|
=> throw new NotSupportedException("GetByIdAsync<T> non è supportato per FoxPro.");
|
|
|
|
public Task<IEnumerable<T>> ExecuteQueryAsync<T>(string sql, params object[] parameters) where T : class
|
|
=> throw new NotSupportedException("ExecuteQueryAsync<T> tipizzato non è supportato per FoxPro. Usare ExecuteRawQueryAsync.");
|
|
|
|
public void Dispose()
|
|
{
|
|
// Nessuna risorsa persistente: ogni operazione apre/chiude i file.
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
}
|