[Feature] Supporto Visual FoxPro / dBase come sorgente dati (managed, sola lettura)
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>
This commit is contained in:
@@ -90,6 +90,19 @@ public partial class DataCoupler : ComponentBase
|
||||
var credential = databaseCredentials.FirstOrDefault(c => c.Name == selectedDatabaseCredential);
|
||||
return credential?.DatabaseType == DatabaseType.OleDb;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifica se la credenziale database selezionata è di tipo Visual FoxPro
|
||||
/// (sorgente file-based in sola lettura).
|
||||
/// </summary>
|
||||
protected bool IsFoxproConnection()
|
||||
{
|
||||
if (string.IsNullOrEmpty(selectedDatabaseCredential))
|
||||
return false;
|
||||
|
||||
var credential = databaseCredentials.FirstOrDefault(c => c.Name == selectedDatabaseCredential);
|
||||
return credential?.DatabaseType == DatabaseType.Foxpro;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gestisce il cambio di credenziale database selezionata
|
||||
@@ -760,6 +773,7 @@ public partial class DataCoupler : ComponentBase
|
||||
{
|
||||
DatabaseType.SqlServer => $"SELECT TOP {limit} * FROM ({baseQuery}) AS subquery",
|
||||
DatabaseType.OleDb => $"SELECT TOP {limit} * FROM ({baseQuery}) AS subquery",
|
||||
DatabaseType.Foxpro => $"SELECT TOP {limit} * FROM ({baseQuery}) AS subquery",
|
||||
DatabaseType.Oracle => $"SELECT * FROM ({baseQuery}) WHERE ROWNUM <= {limit}",
|
||||
DatabaseType.MySql => $"{baseQuery} LIMIT {limit}",
|
||||
DatabaseType.PostgreSql => $"{baseQuery} LIMIT {limit}",
|
||||
|
||||
@@ -244,6 +244,7 @@ else
|
||||
<option value="@CredentialManager.Models.DatabaseType.SapHana">SAP HANA</option>*@
|
||||
<option value="@CredentialManager.Models.DatabaseType.Odbc">ODBC</option>
|
||||
<option value="@CredentialManager.Models.DatabaseType.OleDb">OLE DB</option>
|
||||
<option value="@CredentialManager.Models.DatabaseType.Foxpro">Visual FoxPro (.dbc / .dbf)</option>
|
||||
</InputSelect>
|
||||
</div>
|
||||
</div>
|
||||
@@ -609,6 +610,76 @@ else
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else if (currentDatabaseCredential.DatabaseType == CredentialManager.Models.DatabaseType.Foxpro)
|
||||
{
|
||||
<!-- Configurazione Visual FoxPro -->
|
||||
<div class="card mb-3">
|
||||
<div class="card-header bg-success text-white">
|
||||
<h6 class="mb-0"><i class="oi oi-hard-drive"></i> Configurazione Visual FoxPro</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="alert alert-info py-2">
|
||||
<i class="oi oi-info"></i> Lettura <strong>managed</strong> dei file <code>.dbf</code>/<code>.fpt</code>/<code>.dbc</code>:
|
||||
funziona a <strong>64-bit senza installare alcun provider</strong>. FoxPro è utilizzabile come
|
||||
<strong>sorgente</strong> (sola lettura).
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Percorso Database FoxPro *</label>
|
||||
<InputText class="form-control font-monospace"
|
||||
@bind-Value="currentDatabaseCredential.DatabaseName"
|
||||
placeholder="es. C:\dati\Data.dbc oppure C:\dati" />
|
||||
<small class="form-text text-muted">
|
||||
<strong>Database container</strong>: percorso completo al file <code>.dbc</code>
|
||||
(es. <code>C:\Users\aless\Desktop\data\Data.dbc</code>).<br />
|
||||
<strong>Tabelle libere</strong>: percorso della <strong>cartella</strong> che contiene i file <code>.dbf</code>
|
||||
(es. <code>C:\Users\aless\Desktop\data</code>).
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Code Page (encoding)</label>
|
||||
<select class="form-select" @bind="foxproCodePage">
|
||||
<option value="">-- Auto / Default (1252) --</option>
|
||||
<option value="1252">1252 — Europa occidentale (ANSI)</option>
|
||||
<option value="1250">1250 — Europa centrale</option>
|
||||
<option value="1251">1251 — Cirillico</option>
|
||||
<option value="850">850 — DOS Latin-1</option>
|
||||
<option value="437">437 — DOS US</option>
|
||||
<option value="65001">65001 — UTF-8</option>
|
||||
</select>
|
||||
<small class="form-text text-muted">Per dati italiani lasciare 1252.</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Record cancellati</label>
|
||||
<div class="form-check mt-2">
|
||||
<input class="form-check-input" type="checkbox" id="foxproIncludeDeleted" @bind="foxproIncludeDeleted" />
|
||||
<label class="form-check-label" for="foxproIncludeDeleted">
|
||||
Includi i record marcati come cancellati
|
||||
</label>
|
||||
</div>
|
||||
<small class="form-text text-muted">Di default i record cancellati vengono esclusi.</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-light border py-2 small mb-3">
|
||||
<i class="oi oi-list"></i> Tipi VFP supportati: Character, Memo, Numeric, Float, Double,
|
||||
Integer, Currency, Date, DateTime, Logical. I campi memo (<code>.fpt</code>) e gli indici
|
||||
(<code>.cdx</code>) vengono rilevati automaticamente accanto al <code>.dbf</code>.
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Anteprima percorso/connessione</label>
|
||||
<textarea class="form-control font-monospace" rows="2" readonly>@GetFoxproConnectionStringPreview()</textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<!-- Configurazione Standard Database -->
|
||||
@@ -1035,6 +1106,10 @@ else
|
||||
private string oleDbCollatingSequence = string.Empty;
|
||||
private string oleDbDeleted = string.Empty;
|
||||
|
||||
// Visual FoxPro specific state
|
||||
private string foxproCodePage = string.Empty;
|
||||
private bool foxproIncludeDeleted = false;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{ await RefreshCredentials();
|
||||
CheckForProblematicCredentials();
|
||||
@@ -1131,7 +1206,15 @@ else
|
||||
if (currentDatabaseCredential.AdditionalParameters?.ContainsKey("DELETED") == true)
|
||||
oleDbDeleted = currentDatabaseCredential.AdditionalParameters["DELETED"];
|
||||
}
|
||||
|
||||
|
||||
// Se è Visual FoxPro, ripristina encoding e gestione record cancellati
|
||||
if (currentDatabaseCredential.DatabaseType == DatabaseType.Foxpro)
|
||||
{
|
||||
foxproCodePage = currentDatabaseCredential.AdditionalParameters?.GetValueOrDefault("Encoding") ?? string.Empty;
|
||||
var incDel = currentDatabaseCredential.AdditionalParameters?.GetValueOrDefault("IncludeDeleted", "") ?? "";
|
||||
foxproIncludeDeleted = string.Equals(incDel, "true", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
showDatabaseModal = true;
|
||||
}
|
||||
|
||||
@@ -1155,6 +1238,26 @@ else
|
||||
currentDatabaseCredential.AdditionalParameters.Remove("DELETED");
|
||||
}
|
||||
|
||||
// Sincronizza i parametri Visual FoxPro negli AdditionalParameters
|
||||
if (currentDatabaseCredential.DatabaseType == DatabaseType.Foxpro)
|
||||
{
|
||||
currentDatabaseCredential.AdditionalParameters ??= new Dictionary<string, string>();
|
||||
|
||||
if (!string.IsNullOrEmpty(foxproCodePage))
|
||||
currentDatabaseCredential.AdditionalParameters["Encoding"] = foxproCodePage;
|
||||
else
|
||||
currentDatabaseCredential.AdditionalParameters.Remove("Encoding");
|
||||
|
||||
if (foxproIncludeDeleted)
|
||||
currentDatabaseCredential.AdditionalParameters["IncludeDeleted"] = "true";
|
||||
else
|
||||
currentDatabaseCredential.AdditionalParameters.Remove("IncludeDeleted");
|
||||
|
||||
// Sorgente file-based: nessun host/porta/credenziali
|
||||
currentDatabaseCredential.Host = string.Empty;
|
||||
currentDatabaseCredential.Port = 0;
|
||||
}
|
||||
|
||||
await CredentialService.SaveDatabaseCredentialAsync(currentDatabaseCredential);
|
||||
await JSRuntime.InvokeVoidAsync("alert", "Credenziale database salvata con successo!");
|
||||
CloseDatabaseModal();
|
||||
@@ -1227,6 +1330,15 @@ else
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (currentDatabaseCredential.DatabaseType == DatabaseType.Foxpro)
|
||||
{
|
||||
// Visual FoxPro: sorgente file-based, richiede solo il percorso (DatabaseName)
|
||||
if (string.IsNullOrWhiteSpace(currentDatabaseCredential.DatabaseName))
|
||||
{
|
||||
await JSRuntime.InvokeVoidAsync("alert", "Inserisci il percorso del database FoxPro (file .dbc o cartella con i .dbf).");
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Altri database: validazione standard (Host, Username, Password)
|
||||
@@ -1281,7 +1393,15 @@ else
|
||||
{
|
||||
await LoadOdbcData();
|
||||
}
|
||||
|
||||
|
||||
// Se è Visual FoxPro, reimposta i valori di default (sola lettura, file-based)
|
||||
if (currentDatabaseCredential.DatabaseType == DatabaseType.Foxpro)
|
||||
{
|
||||
currentDatabaseCredential.AdditionalParameters ??= new Dictionary<string, string>();
|
||||
foxproCodePage = string.Empty;
|
||||
foxproIncludeDeleted = false;
|
||||
}
|
||||
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
@@ -1531,6 +1651,36 @@ else
|
||||
}
|
||||
}
|
||||
|
||||
private string GetFoxproConnectionStringPreview()
|
||||
{
|
||||
if (currentDatabaseCredential.DatabaseType != DatabaseType.Foxpro)
|
||||
return string.Empty;
|
||||
|
||||
try
|
||||
{
|
||||
currentDatabaseCredential.AdditionalParameters ??= new Dictionary<string, string>();
|
||||
|
||||
if (!string.IsNullOrEmpty(foxproCodePage))
|
||||
currentDatabaseCredential.AdditionalParameters["Encoding"] = foxproCodePage;
|
||||
else
|
||||
currentDatabaseCredential.AdditionalParameters.Remove("Encoding");
|
||||
|
||||
if (foxproIncludeDeleted)
|
||||
currentDatabaseCredential.AdditionalParameters["IncludeDeleted"] = "true";
|
||||
else
|
||||
currentDatabaseCredential.AdditionalParameters.Remove("IncludeDeleted");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(currentDatabaseCredential.DatabaseName))
|
||||
return "(inserire il percorso al file .dbc o alla cartella di .dbf)";
|
||||
|
||||
return ConnectionStringBuilder.BuildConnectionString(currentDatabaseCredential);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return $"Errore nella generazione: {ex.Message}";
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -91,6 +91,14 @@ namespace Data_Coupler.Services
|
||||
return new DataConnection.DB.OleDbDatabaseManager(connectionString);
|
||||
}
|
||||
|
||||
// Per Visual FoxPro, usa FoxProDatabaseManager (lettura managed di .dbf/.fpt/.dbc, 64-bit)
|
||||
if (credential.DatabaseType == DatabaseType.Foxpro)
|
||||
{
|
||||
var connectionString = CredentialManager.Models.ConnectionStringBuilder.BuildConnectionString(credential);
|
||||
_logger.LogInformation("Creando FoxProDatabaseManager (sola lettura) per {CredentialName}", credentialName);
|
||||
return new DataConnection.DB.FoxProDatabaseManager(connectionString);
|
||||
}
|
||||
|
||||
// Per altri database, usa EFCoreDatabaseManager
|
||||
var dbManagerOptions = await _credentialService.GetDbManagerOptionsAsync(credential.Name);
|
||||
return new EFCoreDatabaseManager(dbManagerOptions);
|
||||
|
||||
Reference in New Issue
Block a user