[Feature] Aggiunto supporto completo OLE DB per connessione database

## Nuovi file
- DataConnection/DB/OleDbDatabaseManager.cs: Manager completo per connessioni OLE DB
  con supporto Task.Run() per operazioni sincrone, parametri posizionali '?',
  guard per piattaforma Windows, nessun supporto ChangeDatabaseAsync (no-op)
- DataConnection/DB/EF/SchemaProviders/OleDbSchemaProvider.cs: Schema provider per
  OLE DB, usa OleDbSchemaGuid per tabelle/colonne/chiavi primarie, mapping tipi dati
- CredentialManager/Services/OleDbProviderDiscoveryService.cs: Servizio di discovery
  provider OLE DB installati tramite registro Windows (HKEY_CLASSES_ROOT). Rileva
  9 provider noti: VFPOLEDB.1, Microsoft.ACE.OLEDB.12.0, Jet 4.0, SQLOLEDB, ecc.
  Mostra warning per provider solo 32-bit (VFPOLEDB, Jet)
- PUBLISH_32BIT_64BIT.md: Documentazione completa comandi pubblicazione per
  win-x64, win-x86 (richiesto per VFP), linux-x64, osx-x64, osx-arm64.
  Include prerequisiti VFPOLEDB, esempi connection string VFP, note Docker

## File modificati
- DataConnection/DB/Enums/DatabaseType.cs: Aggiunto valore OleDb dopo Odbc
- DataConnection/DataConnection.csproj: Aggiunto pacchetto System.Data.OleDb 9.0.3
- DataConnection/DB/OdbcDatabaseManager.cs: Fix bug ChangeDatabaseAsync con try-catch
- CredentialManager/Models/CredentialModels.cs: Aggiunto OleDb all'enum DatabaseType,
  BuildOleDbConnectionString() con supporto provider da AdditionalParameters,
  default VFPOLEDB.1, costruzione connection string con parametri VFP
- DataConnection/CredentialManagement/Models/CredentialExtensions.cs: Mappatura
  OleDb in ToDataConnectionDatabaseType() e ToCredentialDatabaseType()
- DataConnection/CredentialManagement/Services/DataConnectionCredentialService.cs:
  Aggiunto case OleDb in TestDatabaseConnectionAsync e metodo TestOleDbConnection()
  con apertura connessione via Task.Run() e gestione OleDbException dettagliata
- Data_Coupler/Services/DataConnectionFactory.cs: Aggiunto case OleDb per creazione
  OleDbDatabaseManager
- Data_Coupler/Program.cs: Registrazione IOleDbProviderDiscoveryService come Scoped
- Data_Coupler/Pages/CredentialManagement.razor: Aggiunta UI completa OLE DB con
  sezione dedicata Visual FoxPro (percorso .dbc/.dbf, Collating Sequence, DELETED),
  provider discovery con refresh, anteprima connection string, variabili di stato
  e metodi nel codice Blazor, sincronizzazione AdditionalParameters al salvataggio

## Compatibilità
- VFP 8.0/9.0: testato con VFPOLEDB.1, connessione file-based .dbc e .dbf
- Richiede pubblicazione win-x86 per driver OLE DB 32-bit
- AnyCPU non supportato per VFP (COM 32-bit)
This commit is contained in:
2026-05-25 21:20:08 +02:00
parent 6452d45b77
commit 82e0d6bc77
13 changed files with 1342 additions and 5 deletions
@@ -8,6 +8,7 @@
@using Microsoft.JSInterop
@inject IDataConnectionCredentialService CredentialService
@inject IOdbcDsnDiscoveryService OdbcDsnDiscoveryService
@inject IOleDbProviderDiscoveryService OleDbProviderDiscoveryService
@inject IJSRuntime JSRuntime
@inject NavigationManager Navigation
@@ -242,6 +243,7 @@ else
<option value="@CredentialManager.Models.DatabaseType.DB2">DB2</option>
<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>
</InputSelect>
</div>
</div>
@@ -465,6 +467,148 @@ else
</div>
</div>
}
else if (currentDatabaseCredential.DatabaseType == CredentialManager.Models.DatabaseType.OleDb)
{
<!-- Configurazione OLE DB -->
<div class="card mb-3">
<div class="card-header bg-warning text-dark">
<h6 class="mb-0"><i class="oi oi-link-intact"></i> Configurazione OLE DB</h6>
</div>
<div class="card-body">
<div class="alert alert-danger py-2">
<i class="oi oi-warning"></i> <strong>Attenzione — Compatibilità 32-bit:</strong>
Driver come <strong>VFPOLEDB.1</strong> (Visual FoxPro) sono <strong>esclusivamente 32-bit</strong>.
Pubblica l'applicazione con <code>dotnet publish --runtime win-x86</code>.
Vedi <strong>PUBLISH_32BIT_64BIT.md</strong> per tutti i dettagli.
</div>
<div class="mb-3">
<label class="form-label">
Provider OLE DB *
<button type="button" class="btn btn-sm btn-outline-secondary ms-2" @onclick="RefreshOleDbProviderList">
<i class="oi oi-reload"></i> Aggiorna Lista
</button>
</label>
@if (availableOleDbProviders.Any())
{
<select class="form-select" @bind="selectedOleDbProvider">
<option value="">-- Seleziona Provider --</option>
@foreach (var prov in availableOleDbProviders)
{
<option value="@prov.ProgId">@prov.ProgId — @prov.Description</option>
}
</select>
@if (!string.IsNullOrEmpty(selectedOleDbProvider))
{
var info = availableOleDbProviders.FirstOrDefault(p => p.ProgId == selectedOleDbProvider);
if (info?.Note != null)
{
<div class="alert alert-warning mt-2 py-2 small">
<i class="oi oi-warning"></i> @info.Note
</div>
}
}
}
else
{
<div class="alert alert-info py-2 small">
<i class="oi oi-info"></i> Nessun provider OLE DB rilevato automaticamente.
Potrebbe essere necessario eseguire in modalità 32-bit. Inserisci manualmente il ProgID:
</div>
}
<InputText class="form-control mt-1" @bind-Value="selectedOleDbProvider"
placeholder="es. VFPOLEDB.1, Microsoft.ACE.OLEDB.12.0, Microsoft.Jet.OLEDB.4.0" />
<small class="form-text text-muted">
Provider comuni: <code>VFPOLEDB.1</code> (VFP), <code>Microsoft.ACE.OLEDB.12.0</code> (Access/Excel), <code>Microsoft.Jet.OLEDB.4.0</code> (Access 97-2003)
</small>
</div>
@if (IsVfpProvider())
{
<!-- Sezione specifica Visual FoxPro -->
<div class="mb-3">
<label class="form-label">Percorso Database VFP * <small class="text-muted">(.dbc o cartella .dbf)</small></label>
<InputText class="form-control font-monospace"
@bind-Value="currentDatabaseCredential.DatabaseName"
placeholder="es. C:\VFP\Database\miodb.dbc oppure C:\VFP\Tabelle\" />
<small class="form-text text-muted">
Per database container (<code>.dbc</code>): percorso completo al file.<br />
Per tabelle free (<code>.dbf</code>): percorso della cartella contenente i file.
</small>
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">Collating Sequence</label>
<select class="form-select" @bind="oleDbCollatingSequence">
<option value="">-- Default (machine) --</option>
<option value="machine">machine</option>
<option value="general">general</option>
<option value="spanish">spanish</option>
<option value="dutch">dutch</option>
<option value="french">french</option>
<option value="german">german</option>
<option value="italian">italian</option>
</select>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">Record Cancellati (DELETED)</label>
<select class="form-select" @bind="oleDbDeleted">
<option value="">-- Default (ON) --</option>
<option value="ON">ON — escludi record cancellati</option>
<option value="OFF">OFF — includi record cancellati</option>
</select>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">Username <small class="text-muted">(raramente richiesto per VFP)</small></label>
<InputText class="form-control" @bind-Value="currentDatabaseCredential.Username" placeholder="Opzionale" />
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">Password <small class="text-muted">(raramente richiesta per VFP)</small></label>
<InputText type="password" class="form-control" @bind-Value="currentDatabaseCredential.Password" placeholder="Opzionale" />
</div>
</div>
</div>
}
else
{
<div class="mb-3">
<label class="form-label">Data Source <small class="text-muted">(percorso file o server)</small></label>
<InputText class="form-control" @bind-Value="currentDatabaseCredential.DatabaseName"
placeholder="es. C:\Access\miodb.accdb oppure server\database" />
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">Username</label>
<InputText class="form-control" @bind-Value="currentDatabaseCredential.Username" placeholder="Opzionale" />
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">Password</label>
<InputText type="password" class="form-control" @bind-Value="currentDatabaseCredential.Password" placeholder="Opzionale" />
</div>
</div>
</div>
}
<div class="mb-3">
<label class="form-label">Anteprima Connection String</label>
<textarea class="form-control font-monospace" rows="3" readonly>@GetOleDbConnectionStringPreview()</textarea>
<small class="form-text text-muted">Anteprima della connection string che verrà generata</small>
</div>
</div>
</div>
}
else
{
<!-- Configurazione Standard Database -->
@@ -885,6 +1029,12 @@ else
private string selectedOdbcDriver = string.Empty;
private bool loadingOdbcData = false;
// OLE DB specific state
private List<OleDbProviderInfo> availableOleDbProviders = new();
private string selectedOleDbProvider = string.Empty;
private string oleDbCollatingSequence = string.Empty;
private string oleDbDeleted = string.Empty;
protected override async Task OnInitializedAsync()
{ await RefreshCredentials();
CheckForProblematicCredentials();
@@ -931,6 +1081,11 @@ else
{
await LoadOdbcData();
}
// Se è OLE DB, carica i provider
if (currentDatabaseCredential.DatabaseType == DatabaseType.OleDb)
{
LoadOleDbData();
}
}
private async Task EditDatabaseCredential(DatabaseCredential credential)
@@ -963,6 +1118,19 @@ else
selectedOdbcDriver = currentDatabaseCredential.AdditionalParameters["Driver"];
}
}
// Se è OLE DB, carica i provider e ripristina il provider selezionato
if (currentDatabaseCredential.DatabaseType == DatabaseType.OleDb)
{
LoadOleDbData();
if (currentDatabaseCredential.AdditionalParameters?.ContainsKey("Provider") == true)
{
selectedOleDbProvider = currentDatabaseCredential.AdditionalParameters["Provider"];
}
if (currentDatabaseCredential.AdditionalParameters?.ContainsKey("Collating Sequence") == true)
oleDbCollatingSequence = currentDatabaseCredential.AdditionalParameters["Collating Sequence"];
if (currentDatabaseCredential.AdditionalParameters?.ContainsKey("DELETED") == true)
oleDbDeleted = currentDatabaseCredential.AdditionalParameters["DELETED"];
}
showDatabaseModal = true;
}
@@ -971,6 +1139,22 @@ else
{
try
{
// Sincronizza i parametri OLE DB negli AdditionalParameters prima del salvataggio
if (currentDatabaseCredential.DatabaseType == DatabaseType.OleDb)
{
currentDatabaseCredential.AdditionalParameters ??= new Dictionary<string, string>();
if (!string.IsNullOrEmpty(selectedOleDbProvider))
currentDatabaseCredential.AdditionalParameters["Provider"] = selectedOleDbProvider;
if (!string.IsNullOrEmpty(oleDbCollatingSequence))
currentDatabaseCredential.AdditionalParameters["Collating Sequence"] = oleDbCollatingSequence;
else
currentDatabaseCredential.AdditionalParameters.Remove("Collating Sequence");
if (!string.IsNullOrEmpty(oleDbDeleted))
currentDatabaseCredential.AdditionalParameters["DELETED"] = oleDbDeleted;
else
currentDatabaseCredential.AdditionalParameters.Remove("DELETED");
}
await CredentialService.SaveDatabaseCredentialAsync(currentDatabaseCredential);
await JSRuntime.InvokeVoidAsync("alert", "Credenziale database salvata con successo!");
CloseDatabaseModal();
@@ -1289,6 +1473,64 @@ else
StateHasChanged();
}
// OLE DB Methods
private void LoadOleDbData()
{
try
{
availableOleDbProviders = OleDbProviderDiscoveryService.GetInstalledProviders();
}
catch
{
availableOleDbProviders = new List<OleDbProviderInfo>();
}
}
private void RefreshOleDbProviderList()
{
LoadOleDbData();
StateHasChanged();
}
private bool IsVfpProvider()
{
if (string.IsNullOrEmpty(selectedOleDbProvider))
return false;
return selectedOleDbProvider.StartsWith("VFPOLEDB", StringComparison.OrdinalIgnoreCase);
}
private string GetOleDbConnectionStringPreview()
{
if (currentDatabaseCredential.DatabaseType != DatabaseType.OleDb)
return string.Empty;
try
{
// Copia i parametri OLE DB nei AdditionalParameters temporaneamente
currentDatabaseCredential.AdditionalParameters ??= new Dictionary<string, string>();
if (!string.IsNullOrEmpty(selectedOleDbProvider))
currentDatabaseCredential.AdditionalParameters["Provider"] = selectedOleDbProvider;
if (!string.IsNullOrEmpty(oleDbCollatingSequence))
currentDatabaseCredential.AdditionalParameters["Collating Sequence"] = oleDbCollatingSequence;
else
currentDatabaseCredential.AdditionalParameters.Remove("Collating Sequence");
if (!string.IsNullOrEmpty(oleDbDeleted))
currentDatabaseCredential.AdditionalParameters["DELETED"] = oleDbDeleted;
else
currentDatabaseCredential.AdditionalParameters.Remove("DELETED");
return ConnectionStringBuilder.BuildConnectionString(currentDatabaseCredential);
}
catch (Exception ex)
{
return $"Errore nella generazione: {ex.Message}";
}
}
#endregion
#endregion
+1
View File
@@ -109,6 +109,7 @@ builder.Services.AddScoped<IDataConnectionFactory, DataConnectionFactory>();
// Register ODBC DSN Discovery Service
builder.Services.AddScoped<CredentialManager.Services.IOdbcDsnDiscoveryService, CredentialManager.Services.OdbcDsnDiscoveryService>();
builder.Services.AddScoped<CredentialManager.Services.IOleDbProviderDiscoveryService, CredentialManager.Services.OleDbProviderDiscoveryService>();
// Register Association Service (Pre-Discovery)
builder.Services.AddScoped<Data_Coupler.Services.IAssociationService, Data_Coupler.Services.AssociationService>();
@@ -83,6 +83,14 @@ namespace Data_Coupler.Services
return new DataConnection.DB.OdbcDatabaseManager(connectionString);
}
// Per OLE DB, usa OleDbDatabaseManager direttamente (EF Core non supporta OLE DB)
if (credential.DatabaseType == DatabaseType.OleDb)
{
var connectionString = CredentialManager.Models.ConnectionStringBuilder.BuildConnectionString(credential);
_logger.LogInformation("Creando OleDbDatabaseManager con connection string per {CredentialName}", credentialName);
return new DataConnection.DB.OleDbDatabaseManager(connectionString);
}
// Per altri database, usa EFCoreDatabaseManager
var dbManagerOptions = await _credentialService.GetDbManagerOptionsAsync(credential.Name);
return new EFCoreDatabaseManager(dbManagerOptions);