344853fde9
## Bulk Pre-Discovery e riduzione query SQLite/SOQL
### KeyAssociationService — FindAssociationsByKeyValuesBulkAsync (nuovo)
- Aggiunta query bulk 'WHERE KeyValue IN (...)' per recuperare N associazioni con 1 sola query SQLite
(chunking a 500 chiavi per rispettare il limite ~999 parametri di SQLite)
- Aggiunta interfaccia IKeyAssociationService e delegata in DataConnectionCredentialService / IDataConnectionCredentialService
### AssociationService — BatchFindOrCreateAssociationsAsync (nuovo)
- Nuovo metodo bulk che sostituisce i loop per-record durante l'analisi composite:
1) 1 query SQLite bulk per tutte le chiavi
2) Per le chiavi non trovate: SOQL 'IN (...)' su Salesforce in chunk da 200 via BatchExecuteQueriesAsync
(ceil(K/25) HTTP Composite call invece di K singole)
3) Salvataggio parallelo delle associazioni pre-discovery scoperte
- Fallback per-record automatico per client REST non Salesforce
- Aggiornata interfaccia IAssociationService con documentazione XML completa
### DataCoupler.razor.cs — STEP A/B nel flusso COMPOSITE
- Pre-Discovery spostata FUORI dal loop parallelo (STEP A, prima dell'analisi)
- associationsByKey pre-popolato con BatchFindOrCreateAssociationsAsync
- STEP B: il loop parallelo usa TryGetValue O(1) invece di query async per record
- Rimozione blocco ~40 righe di per-record lookup / fallback duplicati
## Salesforce Composite API — Batch Delete e Patch
### SalesforceServiceClient — metodi batch (nuovi)
- BatchDeleteEntitiesAsync: elimina N record con ceil(N/25) Composite call invece di N
- BatchPatchSingleFieldAsync: aggiorna un singolo campo su N record tramite BatchUpdateEntitiesAsync
### DeletionSyncService — refactoring batch
- ExecuteBatchedSalesforceDeletionsAsync: orchestrazione batch per Delete / Deactivate / Mark su Salesforce
- ExecuteSequentialDeletionsAsync: loop sequenziale esistente estratto in metodo riutilizzabile
- Dispatcher: Salesforce -> batch Composite, altri client REST -> sequenziale
## Supporto OLE DB (database)
### DatabaseSchemaProviderFactory
- Aggiunto case DatabaseType.OleDb -> new OleDbSchemaProvider() nel factory switch
### DatabaseMethod.cs
- Aggiunto metodo IsOleDbConnection() (parallelo a IsOdbcConnection())
- Query validation e manager temporaneo estesi a OLE DB oltre che ODBC
- GetLimitedQuery: aggiunto case OleDb -> 'SELECT TOP N FROM (subquery)'
## Salesforce OAuth2 — fix client_credentials
### CredentialService.cs
- Aggiunto 'GrantType' alla HashSet serviceSpecificKeys per preservarlo nella serializzazione AdditionalParameters
### DataConnectionCredentialService.cs
- Refactored BuildRestServiceOptions in helper statico riutilizzato da entrambi i metodi GetRestServiceOptions
- Mapping coerente ClientId/ClientSecret/GrantType per Salesforce (allineato a DataConnectionFactory)
- TestSalesforceOAuthLogin: branch esplicito per client_credentials (no username/password/token)
con validazione preventiva ClientId+ClientSecret obbligatori
- Log flow label (password|client_credentials) in tutti i messaggi di autenticazione
## VS Code tasks
### .vscode/tasks.json
- Rimosso task generico 'Publish Data_Coupler'
- Aggiunti due task separati: win-x64 e win-x86, entrambi SingleFile + Self-Contained + ReadyToRun
CredentialManager
Un sistema sicuro per la gestione delle credenziali di database e API REST utilizzando Entity Framework Core e SQLite, specificamente progettato per integrarsi con il progetto DataConnection.
Caratteristiche
- ✅ Memorizzazione sicura delle credenziali con crittografia cross-platform
- ✅ Supporto completo per tutti i database del progetto DataConnection:
- SQL Server, MySQL, PostgreSQL, Oracle, SQLite, DB2, SAP HANA
- ✅ Supporto per credenziali API REST (API Key, Basic Auth, Bearer Token)
- ✅ Generazione automatica di connection string per tutti i tipi di database
- ✅ Database SQLite con Entity Framework Core
- ✅ Creazione automatica del database se non esiste
- ✅ Logging integrato
- ✅ Cross-platform (Windows, Linux, macOS)
- ✅ Dependency Injection pronto
- ✅ Validazione delle credenziali
- ✅ Metodi di utilità per integrazione con DataConnection
Tipi di Database Supportati
Il CredentialManager supporta tutti i tipi di database gestiti dal progetto DataConnection:
- SQL Server (porta predefinita: 1433)
- MySQL (porta predefinita: 3306)
- PostgreSQL (porta predefinita: 5432)
- Oracle (porta predefinita: 1521)
- SQLite (database locale)
- DB2 (porta predefinita: 50000)
- SAP HANA (porta predefinita: 30015)
Installazione
- Aggiungi il progetto CredentialManager alla tua soluzione
- Referenzia il progetto nel tuo progetto principale
Utilizzo Base
Utilizzo Standalone
using CredentialManager;
using CredentialManager.Models;
using CredentialManager.Integration;
// Crea un'istanza del servizio
var credentialService = await CredentialManagerFactory.CreateAsync();
// Salva una credenziale database con validazione
var dbCredential = new DatabaseCredential
{
Name = "Database Produzione",
DatabaseType = DatabaseType.SqlServer,
Host = "sql-server.company.com",
Port = 1433, // Si può omettere, verrà usata la porta predefinita
DatabaseName = "ProductionDB",
Username = "dbuser",
Password = "secretpassword",
CommandTimeout = 30,
IgnoreSslErrors = false,
AdditionalParameters = new Dictionary<string, string>
{
["Encrypt"] = "True",
["TrustServerCertificate"] = "True"
}
};
// Salva con validazione automatica
var id = await credentialService.SaveValidatedDatabaseCredentialAsync(dbCredential);
// Genera automaticamente la connection string
var connectionString = dbCredential.ToConnectionString();
Console.WriteLine($"Connection String: {connectionString}");
Integrazione con DataConnection
using CredentialManager.Integration;
// Ottieni credenziale validata
var credential = await credentialService.GetValidatedDatabaseCredentialAsync("Database Produzione");
// Converti per l'uso con DbManagerOptions
var dbOptions = credential.ToDbManagerOptions();
// Oppure ottieni direttamente la connection string
var connectionString = credential.ToConnectionString();
Utilizzo con Dependency Injection
using CredentialManager;
using Microsoft.Extensions.DependencyInjection;
var services = new ServiceCollection();
// Registra i servizi
services.AddLogging(builder => builder.AddConsole());
services.AddCredentialManager("path/to/credentials.db"); // Percorso opzionale
var serviceProvider = services.BuildServiceProvider();
// Inizializza il database
await serviceProvider.InitializeCredentialManagerAsync();
// Usa il servizio
var credentialService = serviceProvider.GetRequiredService<ICredentialService>();
Tipi di Credenziali Supportate
Credenziali Database
var dbCredential = new DatabaseCredential
{
Name = "Nome Univoco",
DatabaseType = DatabaseType.SqlServer, // Tutti i tipi supportati da DataConnection
Host = "server.company.com",
Port = 1433, // Opzionale, usa porta predefinita se omesso
DatabaseName = "MyDatabase",
Username = "dbuser",
Password = "password", // Verrà crittata automaticamente
ConnectionString = "Server=...", // Opzionale, stringa di connessione completa
CommandTimeout = 30, // Timeout comandi in secondi
IgnoreSslErrors = false, // Per connessioni SSL non verificate
AdditionalParameters = new Dictionary<string, string>
{
["Encrypt"] = "True",
["TrustServerCertificate"] = "True"
}
};
await credentialService.SaveValidatedDatabaseCredentialAsync(dbCredential);
Credenziali REST API
var apiCredential = new RestApiCredential
{
Name = "API Esterna",
BaseUrl = "https://api.external-service.com",
ApiKey = "your-api-key", // Verrà crittata automaticamente
Username = "apiuser", // Per Basic Auth
Password = "apipass", // Per Basic Auth, verrà crittata
AuthToken = "bearer-token", // Per Bearer Auth, verrà crittata
TimeoutSeconds = 60, // Timeout richieste
IgnoreSslErrors = false, // Per API con certificati non verificati
Headers = new Dictionary<string, string>
{
["Content-Type"] = "application/json",
["Accept"] = "application/json"
},
AdditionalParameters = new Dictionary<string, string>
{
["CustomParam"] = "value"
}
};
await credentialService.SaveValidatedRestApiCredentialAsync(apiCredential);
Generazione Connection String
Il CredentialManager può generare automaticamente connection string per tutti i tipi di database:
// Genera connection string automaticamente
var connectionString = credential.ToConnectionString();
// Connection string specifiche per tipo di database:
// SQL Server: "Server=host,port;Database=dbname;User Id=user;Password=pass;Connection Timeout=30"
// MySQL: "Server=host;Port=port;Database=dbname;Uid=user;Pwd=pass;Connection Timeout=30"
// PostgreSQL: "Host=host;Port=port;Database=dbname;Username=user;Password=pass;Timeout=30"
// Oracle: "Data Source=host:port/dbname;User Id=user;Password=pass;Connection Timeout=30"
// SQLite: "Data Source=dbname"
// DB2: "Server=host:port;Database=dbname;UID=user;PWD=pass;Connection Timeout=30"
// SAP HANA: "Server=host:port;DatabaseName=dbname;UserID=user;Password=pass;Connection Timeout=30"
Validazione delle Credenziali
using CredentialManager.Integration;
// Validazione manuale
var errors = DataConnectionHelper.ValidateDatabaseCredential(credential);
if (errors.Any())
{
Console.WriteLine($"Errori: {string.Join(", ", errors)}");
}
// Validazione automatica durante il salvataggio
try
{
await credentialService.SaveValidatedDatabaseCredentialAsync(credential);
}
catch (ArgumentException ex)
{
Console.WriteLine($"Credenziale non valida: {ex.Message}");
}
Operazioni Principali
Salvare Credenziali
// Con validazione automatica (raccomandato)
var dbId = await credentialService.SaveValidatedDatabaseCredentialAsync(databaseCredential);
var apiId = await credentialService.SaveValidatedRestApiCredentialAsync(apiCredential);
// Senza validazione
var dbId = await credentialService.SaveDatabaseCredentialAsync(databaseCredential);
var apiId = await credentialService.SaveRestApiCredentialAsync(apiCredential);
Recuperare Credenziali
// Con validazione automatica (raccomandato)
var dbCred = await credentialService.GetValidatedDatabaseCredentialAsync("Database Produzione");
var apiCred = await credentialService.GetValidatedRestApiCredentialAsync("API Esterna");
// Senza validazione
var dbCred = await credentialService.GetDatabaseCredentialAsync("Database Produzione");
var apiCred = await credentialService.GetRestApiCredentialAsync("API Esterna");
// Per ID
var dbCred = await credentialService.GetDatabaseCredentialAsync(1);
var apiCred = await credentialService.GetRestApiCredentialAsync(2);
// Tutte le credenziali di un tipo
var allDbCreds = await credentialService.GetAllDatabaseCredentialsAsync();
var allApiCreds = await credentialService.GetAllRestApiCredentialsAsync();
Eliminare Credenziali
// Per nome
await credentialService.DeleteCredentialAsync("Nome Credenziale");
// Per ID
await credentialService.DeleteCredentialAsync(1);
Ottenere Lista Nomi
// Tutti i nomi
var allNames = await credentialService.GetCredentialNamesAsync();
// Solo nomi di un tipo specifico
var dbNames = await credentialService.GetCredentialNamesAsync(CredentialType.Database);
var apiNames = await credentialService.GetCredentialNamesAsync(CredentialType.RestApi);
Configurazione
Percorso Database Personalizzato
// Specificare un percorso personalizzato per il database
services.AddCredentialManager("/custom/path/credentials.db");
// O per utilizzo standalone
var service = await CredentialManagerFactory.CreateAsync("/custom/path/credentials.db");
Logging Personalizzato
var loggerFactory = LoggerFactory.Create(builder =>
builder.AddConsole().SetMinimumLevel(LogLevel.Debug));
var service = await CredentialManagerFactory.CreateAsync(
databasePath: "credentials.db",
loggerFactory: loggerFactory);
Sicurezza
- Le password, API key e token sono crittati utilizzando:
- Windows:
ProtectedDatacon entropia personalizzata - Altri OS: AES-256 con chiave derivata
- Windows:
- I dati sono memorizzati localmente in un database SQLite
- Le credenziali eliminate sono contrassegnate come inattive (soft delete)
- Validazione automatica dei dati di input
Struttura Database
Il database SQLite viene creato automaticamente con la seguente struttura:
CREATE TABLE Credentials (
Id INTEGER PRIMARY KEY AUTOINCREMENT,
Name NVARCHAR(100) NOT NULL UNIQUE,
Type NVARCHAR(50) NOT NULL,
DatabaseType NVARCHAR(50),
Host NVARCHAR(200),
Port INTEGER,
DatabaseName NVARCHAR(100),
Username NVARCHAR(100),
EncryptedPassword TEXT,
ConnectionString NVARCHAR(500),
EncryptedApiKey NVARCHAR(500),
EncryptedAuthToken NVARCHAR(500),
CommandTimeout INTEGER DEFAULT 30,
TimeoutSeconds INTEGER DEFAULT 100,
IgnoreSslErrors INTEGER DEFAULT 0,
Headers NVARCHAR(2000),
AdditionalParameters NVARCHAR(2000),
CreatedAt DATETIME NOT NULL,
UpdatedAt DATETIME,
CreatedBy NVARCHAR(100),
IsActive INTEGER NOT NULL DEFAULT 1
);
Porte Predefinite
Il sistema assegna automaticamente le porte predefinite se non specificate:
- SQL Server: 1433
- MySQL: 3306
- PostgreSQL: 5432
- Oracle: 1521
- DB2: 50000
- SAP HANA: 30015
- SQLite: N/A (database locale)
Requisiti
- .NET 9.0
- Entity Framework Core 9.0
- SQLite
Note
- Il database viene creato nella cartella
%APPDATA%/CredentialManager/se non specificato un percorso personalizzato - Tutte le operazioni sono asincrone
- Il servizio è thread-safe
- Le migrazioni del database vengono applicate automaticamente all'avvio
- Piena compatibilità con il progetto DataConnection