[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
@@ -0,0 +1,335 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.OleDb;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using DataConnection.Interfaces;
namespace DataConnection.EF.SchemaProviders;
/// <summary>
/// Provider di schema per database OLE DB (incluso Visual FoxPro)
/// Utilizza GetOleDbSchemaTable per ottenere metadati in modo compatibile con VFP e altri provider OLE DB
/// </summary>
public class OleDbSchemaProvider : IDatabaseSchemaProvider
{
public async Task<IDictionary<string, IEnumerable<DbColumnInfo>>> GetDatabaseSchemaAsync(string connectionString)
{
var result = new Dictionary<string, IEnumerable<DbColumnInfo>>();
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
throw new PlatformNotSupportedException("OLE DB è supportato solo su Windows.");
try
{
using var connection = new OleDbConnection(connectionString);
await Task.Run(() => connection.Open());
Console.WriteLine($"OLE DB Schema Provider - Provider: {connection.Provider}");
var tableNames = GetTableNamesFromConnection(connection);
Console.WriteLine($"Trovate {tableNames.Count} tabelle");
foreach (var tableName in tableNames)
{
try
{
var columns = GetTableColumnsFromConnection(connection, tableName);
if (columns.Any())
{
result[tableName] = columns;
Console.WriteLine($"Tabella {tableName}: {columns.Count()} colonne");
}
}
catch (Exception ex)
{
Console.WriteLine($"Errore nel leggere le colonne della tabella {tableName}: {ex.Message}");
}
}
}
catch (Exception ex)
{
Console.WriteLine($"Errore in OleDbSchemaProvider.GetDatabaseSchemaAsync: {ex.Message}");
throw;
}
return result;
}
public async Task<IEnumerable<string>> GetTableNamesAsync(string connectionString)
{
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
return Enumerable.Empty<string>();
try
{
using var connection = new OleDbConnection(connectionString);
await Task.Run(() => connection.Open());
return GetTableNamesFromConnection(connection);
}
catch (Exception ex)
{
Console.WriteLine($"Errore in OleDbSchemaProvider.GetTableNamesAsync: {ex.Message}");
return Enumerable.Empty<string>();
}
}
public async Task<IEnumerable<DbColumnInfo>> GetTableSchemaAsync(string connectionString, string tableName)
{
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
return Enumerable.Empty<DbColumnInfo>();
try
{
using var connection = new OleDbConnection(connectionString);
await Task.Run(() => connection.Open());
return GetTableColumnsFromConnection(connection, tableName);
}
catch (Exception ex)
{
Console.WriteLine($"Errore in OleDbSchemaProvider.GetTableSchemaAsync per {tableName}: {ex.Message}");
return Enumerable.Empty<DbColumnInfo>();
}
}
public async Task<IEnumerable<string>> GetAvailableDatabasesAsync(string connectionString)
{
// OLE DB file-based (VFP, Access) non supporta listing di database multipli
try
{
using var connection = new OleDbConnection(connectionString);
await Task.Run(() => connection.Open());
var db = connection.Database;
return string.IsNullOrEmpty(db) ? Enumerable.Empty<string>() : new[] { db };
}
catch
{
return Enumerable.Empty<string>();
}
}
private static List<string> GetTableNamesFromConnection(OleDbConnection connection)
{
var tableNames = new List<string>();
try
{
// Usa GetOleDbSchemaTable - più compatibile con VFP rispetto a GetSchema()
var restrictions = new object?[] { null, null, null, "TABLE" };
var tablesSchema = connection.GetOleDbSchemaTable(OleDbSchemaGuid.Tables, restrictions);
if (tablesSchema != null)
{
foreach (DataRow row in tablesSchema.Rows)
{
var tableName = row["TABLE_NAME"]?.ToString();
if (!string.IsNullOrEmpty(tableName))
tableNames.Add(tableName);
}
}
}
catch (Exception ex)
{
Console.WriteLine($"GetOleDbSchemaTable Tables fallito, tentativo con GetSchema: {ex.Message}");
// Fallback a GetSchema per provider che non supportano GetOleDbSchemaTable
try
{
var tablesSchema = connection.GetSchema("Tables");
tableNames = tablesSchema.AsEnumerable()
.Where(row =>
{
var t = row["TABLE_TYPE"]?.ToString();
return t == "TABLE" || t == "BASE TABLE";
})
.Select(row => row["TABLE_NAME"]?.ToString() ?? string.Empty)
.Where(n => !string.IsNullOrEmpty(n))
.ToList();
}
catch (Exception ex2)
{
Console.WriteLine($"GetSchema Tables fallito anche: {ex2.Message}");
}
}
return tableNames.OrderBy(t => t).ToList();
}
private static List<DbColumnInfo> GetTableColumnsFromConnection(OleDbConnection connection, string tableName)
{
var columns = new List<DbColumnInfo>();
try
{
// Ottieni primary keys
var primaryKeys = GetPrimaryKeys(connection, tableName);
// Ottieni colonne via GetOleDbSchemaTable
var restrictions = new object?[] { null, null, tableName, null };
var columnsSchema = connection.GetOleDbSchemaTable(OleDbSchemaGuid.Columns, restrictions);
if (columnsSchema == null)
return columns;
// Ordina per posizione ordinale
var rows = columnsSchema.AsEnumerable()
.OrderBy(r => r.IsNull("ORDINAL_POSITION") ? 0 : Convert.ToInt32(r["ORDINAL_POSITION"]))
.ToList();
foreach (DataRow row in rows)
{
var columnName = row["COLUMN_NAME"]?.ToString();
if (string.IsNullOrEmpty(columnName))
continue;
// DATA_TYPE è un int (OleDbType enum value)
int oleDbTypeInt = row.IsNull("DATA_TYPE") ? 0 : Convert.ToInt32(row["DATA_TYPE"]);
var dataType = MapOleDbTypeToString(oleDbTypeInt);
// Formato con dimensioni
var columnSize = row.IsNull("CHARACTER_MAXIMUM_LENGTH") ? 0 : Convert.ToInt32(row["CHARACTER_MAXIMUM_LENGTH"]);
var numericPrecision = row.IsNull("NUMERIC_PRECISION") ? 0 : Convert.ToInt32(row["NUMERIC_PRECISION"]);
var numericScale = row.IsNull("NUMERIC_SCALE") ? 0 : Convert.ToInt32(row["NUMERIC_SCALE"]);
var formattedType = FormatDataType(dataType, columnSize, numericPrecision, numericScale);
bool isNullable = true;
if (!row.IsNull("IS_NULLABLE"))
isNullable = Convert.ToBoolean(row["IS_NULLABLE"]);
columns.Add(new DbColumnInfo
{
Name = columnName,
DataType = formattedType,
IsNullable = isNullable,
IsPrimaryKey = primaryKeys.Contains(columnName, StringComparer.OrdinalIgnoreCase)
});
}
}
catch (Exception ex)
{
Console.WriteLine($"Errore nel recuperare le colonne per {tableName}: {ex.Message}");
}
return columns;
}
private static HashSet<string> GetPrimaryKeys(OleDbConnection connection, string tableName)
{
var primaryKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
try
{
var restrictions = new object?[] { null, null, tableName };
var pkSchema = connection.GetOleDbSchemaTable(OleDbSchemaGuid.Primary_Keys, restrictions);
if (pkSchema != null)
{
foreach (DataRow row in pkSchema.Rows)
{
var col = row["COLUMN_NAME"]?.ToString();
if (!string.IsNullOrEmpty(col))
primaryKeys.Add(col);
}
}
}
catch (Exception ex)
{
Console.WriteLine($"Primary keys non disponibili per {tableName}: {ex.Message}");
// Fallback: prova con Indexes
try
{
var restrictions = new object?[] { null, null, null, null, tableName };
var idxSchema = connection.GetOleDbSchemaTable(OleDbSchemaGuid.Indexes, restrictions);
if (idxSchema != null)
{
foreach (DataRow row in idxSchema.Rows)
{
if (row["PRIMARY_KEY"] is bool pk && pk)
{
var col = row["COLUMN_NAME"]?.ToString();
if (!string.IsNullOrEmpty(col))
primaryKeys.Add(col);
}
}
}
}
catch { /* Se anche questo fallisce, ignora */ }
}
return primaryKeys;
}
private static string MapOleDbTypeToString(int oleDbTypeInt)
{
// Mappa OleDbType enum (int) a nome leggibile
// https://learn.microsoft.com/en-us/dotnet/api/system.data.oledb.oledbtype
return oleDbTypeInt switch
{
2 => "SmallInt",
3 => "Integer",
4 => "Single",
5 => "Double",
6 => "Currency",
7 => "Date",
8 => "VarChar", // BSTR
9 => "IDispatch",
10 => "Error",
11 => "Boolean",
12 => "Variant",
13 => "IUnknown",
14 => "Decimal",
16 => "TinyInt",
17 => "UnsignedTinyInt",
18 => "UnsignedSmallInt",
19 => "UnsignedInt",
20 => "BigInt",
21 => "UnsignedBigInt",
64 => "DateTime",
65 => "FileTime",
72 => "Guid",
128 => "Binary",
129 => "Char",
130 => "NVarChar", // WChar
131 => "Decimal", // Numeric
132 => "UserDefined",
133 => "Date",
134 => "Time",
135 => "DateTime", // DBTimeStamp
136 => "Variant", // Chapter
138 => "PropVariant",
139 => "VarNumeric",
200 => "VarChar",
201 => "LongVarChar",
202 => "NVarChar", // VarWChar
203 => "NText", // LongVarWChar
204 => "VarBinary",
205 => "Image", // LongVarBinary
_ => $"Type({oleDbTypeInt})"
};
}
private static string FormatDataType(string dataType, int columnSize, int numericPrecision, int numericScale)
{
var upper = dataType.ToUpperInvariant();
if (upper.Contains("DECIMAL") || upper.Contains("NUMERIC"))
{
if (numericPrecision > 0)
return $"{dataType}({numericPrecision},{numericScale})";
}
else if (upper.Contains("CHAR") || upper.Contains("VARCHAR") || upper.Contains("TEXT") || upper.Contains("BINARY"))
{
if (columnSize > 0 && columnSize < 8000)
return $"{dataType}({columnSize})";
else if (columnSize >= 8000)
return $"{dataType}(MAX)";
}
return dataType;
}
}
+2 -1
View File
@@ -12,5 +12,6 @@ public enum DatabaseType
Sqlite,
DB2,
SapHana,
Odbc
Odbc,
OleDb
}
+11 -3
View File
@@ -66,11 +66,19 @@ public class OdbcDatabaseManager : IDatabaseManager
using var connection = new OdbcConnection(_connectionString);
await connection.OpenAsync();
// Cambia database se specificato
// Cambia database se specificato (alcuni driver come VFP non supportano ChangeDatabaseAsync)
if (!string.IsNullOrEmpty(databaseName) && databaseName != _currentDatabase)
{
await connection.ChangeDatabaseAsync(databaseName);
_currentDatabase = databaseName;
try
{
await connection.ChangeDatabaseAsync(databaseName);
_currentDatabase = databaseName;
}
catch (Exception dbChangeEx)
{
Console.WriteLine($"[ODBC] ChangeDatabaseAsync non supportato dal driver ({databaseName}): {dbChangeEx.Message}");
// Continua senza cambiare database (es. driver file-based come VFP)
}
}
using var command = new OdbcCommand(sql, connection);
+379
View File
@@ -0,0 +1,379 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.OleDb;
using System.Linq;
using System.Linq.Expressions;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using DataConnection.EF.SchemaProviders;
using DataConnection.Interfaces;
namespace DataConnection.DB;
/// <summary>
/// Database manager per connessioni OLE DB dirette (es. Visual FoxPro, Access, Jet)
/// Nota: i driver OLE DB come VFPOLEDB.1 sono 32-bit only — pubblicare con --runtime win-x86 se necessario
/// </summary>
public class OleDbDatabaseManager : IDatabaseManager
{
private readonly string _connectionString;
private readonly OleDbSchemaProvider _schemaProvider;
public OleDbDatabaseManager(string connectionString)
{
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
throw new PlatformNotSupportedException("OLE DB è supportato solo su Windows.");
_connectionString = connectionString ?? throw new ArgumentNullException(nameof(connectionString));
_schemaProvider = new OleDbSchemaProvider();
}
public async Task<bool> TestConnectionAsync()
{
try
{
using var connection = new OleDbConnection(_connectionString);
await Task.Run(() => connection.Open());
return true;
}
catch
{
return false;
}
}
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 espressioni LINQ non è supportato per OLE DB. Usare ExecuteRawQueryAsync.");
}
public Task<T?> GetByIdAsync<T>(object id) where T : class
{
throw new NotSupportedException("GetByIdAsync<T> non è supportato per OLE DB. Usare ExecuteRawQueryAsync con clausola WHERE.");
}
public Task<IEnumerable<T>> ExecuteQueryAsync<T>(string sql, params object[] parameters) where T : class
{
throw new NotSupportedException("ExecuteQueryAsync<T> non è supportato per OLE DB. Usare ExecuteRawQueryAsync.");
}
public async Task<List<Dictionary<string, object>>> ExecuteRawQueryAsync(string sql, string databaseName = "", params object[] parameters)
{
var results = new List<Dictionary<string, object>>();
using var connection = new OleDbConnection(_connectionString);
await Task.Run(() => connection.Open());
// OLE DB file-based (VFP, Access) non supporta ChangeDatabaseAsync — il database è nel Data Source
// Ignoriamo databaseName per questa tipologia di provider
using var command = new OleDbCommand(sql, connection);
if (parameters != null && parameters.Length > 0)
{
for (int i = 0; i < parameters.Length; i++)
{
command.Parameters.Add(new OleDbParameter($"@p{i}", parameters[i] ?? DBNull.Value));
}
}
using var reader = await Task.Run(() => command.ExecuteReader());
while (reader.Read())
{
var row = new Dictionary<string, object>();
for (int i = 0; i < reader.FieldCount; i++)
{
var fieldName = reader.GetName(i);
var value = reader.IsDBNull(i) ? DBNull.Value : reader.GetValue(i);
row[fieldName] = value;
}
results.Add(row);
}
return results;
}
public async Task<int> ExecuteCommandAsync(string sql, params object[] parameters)
{
using var connection = new OleDbConnection(_connectionString);
await Task.Run(() => connection.Open());
using var command = new OleDbCommand(sql, connection);
if (parameters != null && parameters.Length > 0)
{
for (int i = 0; i < parameters.Length; i++)
{
command.Parameters.Add(new OleDbParameter($"@p{i}", parameters[i] ?? DBNull.Value));
}
}
return await Task.Run(() => command.ExecuteNonQuery());
}
public async Task<List<string>> GetAvailableDatabasesAsync()
{
var databases = await _schemaProvider.GetAvailableDatabasesAsync(_connectionString);
return databases.ToList();
}
public async Task ChangeDatabaseAsync(string databaseName)
{
// I provider OLE DB file-based (VFP, Access) non supportano il cambio di database a runtime
// Il Data Source nella connection string definisce il database
Console.WriteLine($"[OleDb] ChangeDatabaseAsync ignorato per provider file-based (database: {databaseName})");
await Task.CompletedTask;
}
public async Task<IDictionary<string, IEnumerable<DbColumnInfo>>> GetDatabaseSchemaAsync()
{
return await _schemaProvider.GetDatabaseSchemaAsync(_connectionString);
}
public async Task<IEnumerable<string>> GetTableNamesAsync()
{
return await _schemaProvider.GetTableNamesAsync(_connectionString);
}
public async Task<IEnumerable<DbColumnInfo>> GetTableSchemaAsync(string tableName)
{
return await _schemaProvider.GetTableSchemaAsync(_connectionString, tableName);
}
public async Task<IEnumerable<Dictionary<string, object>>> GetAllRecordsAsync(string tableName)
{
var query = $"SELECT * FROM {tableName}";
return await ExecuteRawQueryAsync(query);
}
public async Task<string?> GetPrimaryKeyFieldAsync(string tableName)
{
try
{
var schema = await GetTableSchemaAsync(tableName);
var pkColumn = schema.FirstOrDefault(c => c.IsPrimaryKey);
return pkColumn?.Name;
}
catch
{
return null;
}
}
public async Task<IEnumerable<IDictionary<string, object?>>> ExecuteQueryAsync(string query, int? maxRows = null)
{
var results = new List<IDictionary<string, object?>>();
using var connection = new OleDbConnection(_connectionString);
await Task.Run(() => connection.Open());
var commandText = maxRows.HasValue ? WrapQueryWithLimit(query, maxRows.Value) : query;
using var command = new OleDbCommand(commandText, connection);
using var reader = await Task.Run(() => command.ExecuteReader());
while (reader.Read())
{
var row = new Dictionary<string, object?>();
for (int i = 0; i < reader.FieldCount; i++)
{
var fieldName = reader.GetName(i);
var value = reader.IsDBNull(i) ? null : reader.GetValue(i);
row[fieldName] = value;
}
results.Add(row);
}
return results;
}
public async Task<int> ExecuteNonQueryAsync(string query)
{
using var connection = new OleDbConnection(_connectionString);
await Task.Run(() => connection.Open());
using var command = new OleDbCommand(query, connection);
return await Task.Run(() => command.ExecuteNonQuery());
}
public async Task<object?> ExecuteScalarAsync(string query)
{
using var connection = new OleDbConnection(_connectionString);
await Task.Run(() => connection.Open());
using var command = new OleDbCommand(query, connection);
return await Task.Run(() => command.ExecuteScalar());
}
public async Task<int> InsertAsync(string tableName, IDictionary<string, object?> data)
{
var columns = string.Join(", ", data.Keys.Select(k => $"[{k}]"));
var parameters = string.Join(", ", data.Keys.Select(_ => "?"));
var query = $"INSERT INTO {tableName} ({columns}) VALUES ({parameters})";
using var connection = new OleDbConnection(_connectionString);
await Task.Run(() => connection.Open());
using var command = new OleDbCommand(query, connection);
foreach (var value in data.Values)
command.Parameters.Add(new OleDbParameter { Value = value ?? DBNull.Value });
return await Task.Run(() => command.ExecuteNonQuery());
}
public async Task<int> UpdateAsync(string tableName, IDictionary<string, object?> data, IDictionary<string, object?> whereClause)
{
var setClause = string.Join(", ", data.Keys.Select(k => $"[{k}] = ?"));
var whereConditions = string.Join(" AND ", whereClause.Keys.Select(k => $"[{k}] = ?"));
var query = $"UPDATE {tableName} SET {setClause} WHERE {whereConditions}";
using var connection = new OleDbConnection(_connectionString);
await Task.Run(() => connection.Open());
using var command = new OleDbCommand(query, connection);
foreach (var value in data.Values)
command.Parameters.Add(new OleDbParameter { Value = value ?? DBNull.Value });
foreach (var value in whereClause.Values)
command.Parameters.Add(new OleDbParameter { Value = value ?? DBNull.Value });
return await Task.Run(() => command.ExecuteNonQuery());
}
public async Task<int> DeleteAsync(string tableName, IDictionary<string, object?> whereClause)
{
var whereConditions = string.Join(" AND ", whereClause.Keys.Select(k => $"[{k}] = ?"));
var query = $"DELETE FROM {tableName} WHERE {whereConditions}";
using var connection = new OleDbConnection(_connectionString);
await Task.Run(() => connection.Open());
using var command = new OleDbCommand(query, connection);
foreach (var value in whereClause.Values)
command.Parameters.Add(new OleDbParameter { Value = value ?? DBNull.Value });
return await Task.Run(() => command.ExecuteNonQuery());
}
public async Task<int> BulkInsertAsync(string tableName, IEnumerable<IDictionary<string, object?>> dataList)
{
int totalInserted = 0;
using var connection = new OleDbConnection(_connectionString);
await Task.Run(() => connection.Open());
using var transaction = connection.BeginTransaction();
try
{
foreach (var data in dataList)
{
var columns = string.Join(", ", data.Keys.Select(k => $"[{k}]"));
var parameters = string.Join(", ", data.Keys.Select(_ => "?"));
var query = $"INSERT INTO {tableName} ({columns}) VALUES ({parameters})";
using var command = new OleDbCommand(query, connection, transaction);
foreach (var value in data.Values)
command.Parameters.Add(new OleDbParameter { Value = value ?? DBNull.Value });
totalInserted += await Task.Run(() => command.ExecuteNonQuery());
}
transaction.Commit();
}
catch
{
transaction.Rollback();
throw;
}
return totalInserted;
}
public async Task<bool> UpsertRecordAsync(string tableName, string keyField, object? keyValue, Dictionary<string, object?> record)
{
try
{
using var connection = new OleDbConnection(_connectionString);
await Task.Run(() => connection.Open());
using var checkCmd = new OleDbCommand($"SELECT COUNT(*) FROM {tableName} WHERE [{keyField}] = ?", connection);
checkCmd.Parameters.Add(new OleDbParameter { Value = keyValue ?? DBNull.Value });
var countResult = await Task.Run(() => checkCmd.ExecuteScalar());
bool exists = Convert.ToInt64(countResult ?? 0L) > 0;
if (exists)
{
var fields = record.Keys.ToList();
var setClauses = fields.Select(f => $"[{f}] = ?").ToList();
var updateSql = $"UPDATE {tableName} SET {string.Join(", ", setClauses)} WHERE [{keyField}] = ?";
using var updateCmd = new OleDbCommand(updateSql, connection);
foreach (var f in fields)
updateCmd.Parameters.Add(new OleDbParameter { Value = record[f] ?? DBNull.Value });
updateCmd.Parameters.Add(new OleDbParameter { Value = keyValue ?? DBNull.Value });
await Task.Run(() => updateCmd.ExecuteNonQuery());
}
else
{
var fields = record.Keys.ToList();
var fieldNames = string.Join(", ", fields.Select(f => $"[{f}]"));
var paramPlaceholders = string.Join(", ", fields.Select(_ => "?"));
var insertSql = $"INSERT INTO {tableName} ({fieldNames}) VALUES ({paramPlaceholders})";
using var insertCmd = new OleDbCommand(insertSql, connection);
foreach (var f in fields)
insertCmd.Parameters.Add(new OleDbParameter { Value = record[f] ?? DBNull.Value });
await Task.Run(() => insertCmd.ExecuteNonQuery());
}
return true;
}
catch (Exception ex)
{
Console.WriteLine($"Errore nell'upsert OLE DB in {tableName}: {ex.Message}");
return false;
}
}
/// <summary>
/// VFP e la maggior parte dei provider OLE DB supportano SELECT TOP N (SQL Server style)
/// </summary>
private static string WrapQueryWithLimit(string query, int maxRows)
{
var upperQuery = query.Trim().ToUpperInvariant();
if (upperQuery.Contains("LIMIT ") || upperQuery.Contains("TOP "))
return query;
if (upperQuery.StartsWith("SELECT "))
return query.Insert(7, $"TOP {maxRows} ");
return $"{query} LIMIT {maxRows}";
}
public void Dispose()
{
// Nessuna risorsa da rilasciare
}
}