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
169 lines
6.5 KiB
C#
169 lines
6.5 KiB
C#
using CredentialManager.Models;
|
|
|
|
namespace CredentialManager.Services;
|
|
|
|
/// <summary>
|
|
/// Interfaccia per il servizio di gestione delle associazioni basate sui valori delle chiavi
|
|
/// </summary>
|
|
public interface IKeyAssociationService
|
|
{
|
|
/// <summary>
|
|
/// Salva una nuova associazione o aggiorna una esistente
|
|
/// </summary>
|
|
Task<int> SaveAssociationAsync(KeyAssociation association);
|
|
|
|
/// <summary>
|
|
/// Versione thread-safe del SaveAssociationAsync che utilizza un DbContext separato per operazioni parallele
|
|
/// </summary>
|
|
Task<int> SaveAssociationParallelAsync(KeyAssociation association);
|
|
|
|
/// <summary>
|
|
/// Cerca un'associazione esistente tramite valore chiave
|
|
/// </summary>
|
|
Task<KeyAssociation?> FindAssociationByKeyValueAsync(string keyValue, string destinationEntity, string restCredentialName);
|
|
|
|
/// <summary>
|
|
/// Cerca un'associazione esistente tramite valore chiave, indipendentemente dalla destinazione
|
|
/// </summary>
|
|
Task<KeyAssociation?> FindAssociationByKeyValueAsync(string keyValue);
|
|
|
|
/// <summary>
|
|
/// Ottiene tutte le associazioni per un'entità di destinazione specifica
|
|
/// </summary>
|
|
Task<List<KeyAssociation>> GetAssociationsByDestinationAsync(string destinationEntity, string restCredentialName);
|
|
|
|
/// <summary>
|
|
/// Ottiene tutte le associazioni attive
|
|
/// </summary>
|
|
Task<List<KeyAssociation>> GetAllActiveAssociationsAsync();
|
|
|
|
/// <summary>
|
|
/// Ottiene tutte le associazioni (attive e non)
|
|
/// </summary>
|
|
Task<List<KeyAssociation>> GetAllAssociationsAsync();
|
|
|
|
/// <summary>
|
|
/// Aggiorna un'associazione esistente
|
|
/// </summary>
|
|
Task<bool> UpdateAssociationAsync(KeyAssociation association);
|
|
|
|
/// <summary>
|
|
/// Disattiva un'associazione
|
|
/// </summary>
|
|
Task<bool> DeactivateAssociationAsync(int id);
|
|
|
|
/// <summary>
|
|
/// Elimina definitivamente un'associazione
|
|
/// </summary>
|
|
Task<bool> DeleteAssociationAsync(int id);
|
|
|
|
/// <summary>
|
|
/// Pulisce le associazioni più vecchie di un determinato periodo
|
|
/// </summary>
|
|
Task<int> CleanupOldAssociationsAsync(TimeSpan olderThan);
|
|
|
|
/// <summary>
|
|
/// Elimina tutte le associazioni per una specifica combinazione entità-credenziale
|
|
/// </summary>
|
|
Task<int> ClearAssociationsAsync(string destinationEntity, string restCredentialName);
|
|
|
|
/// <summary>
|
|
/// Elimina tutte le associazioni nel sistema
|
|
/// </summary>
|
|
Task<int> ClearAllAssociationsAsync();
|
|
|
|
/// <summary>
|
|
/// Verifica se un ID di destinazione esiste ancora nel sistema target
|
|
/// </summary>
|
|
Task<bool> ValidateDestinationIdAsync(string destinationId, string destinationEntity, string restCredentialName);
|
|
|
|
/// <summary>
|
|
/// Ottiene tutte le associazioni con ID di destinazione non validi
|
|
/// </summary>
|
|
Task<List<KeyAssociation>> GetInvalidAssociationsAsync(string destinationEntity, string restCredentialName);
|
|
|
|
/// <summary>
|
|
/// Pulisce le associazioni con ID di destinazione non più validi
|
|
/// </summary>
|
|
Task<int> CleanupInvalidAssociationsAsync(string destinationEntity, string restCredentialName);
|
|
|
|
/// <summary>
|
|
/// Aggiorna la data di ultima verifica per un'associazione
|
|
/// </summary>
|
|
Task<bool> UpdateLastVerifiedAsync(int id);
|
|
|
|
/// <summary>
|
|
/// Ottiene statistiche sulle associazioni
|
|
/// </summary>
|
|
Task<AssociationStatistics> GetStatisticsAsync();
|
|
|
|
/// <summary>
|
|
/// Versione thread-safe per operazioni parallele - Salva una associazione
|
|
/// </summary>
|
|
Task<bool> SaveAssociationParallelAsync(string keyValue, string destinationEntity, string destinationId, string restCredentialName);
|
|
|
|
/// <summary>
|
|
/// Versione thread-safe per operazioni parallele - Trova associazione per valore chiave
|
|
/// </summary>
|
|
Task<KeyAssociation?> FindAssociationByKeyValueParallelAsync(string keyValue, string destinationEntity, string restCredentialName);
|
|
|
|
/// <summary>
|
|
/// Versione thread-safe per operazioni parallele - Trova associazione per valore chiave (solo keyValue)
|
|
/// </summary>
|
|
Task<KeyAssociation?> FindAssociationByKeyValueParallelAsync(string keyValue);
|
|
|
|
/// <summary>
|
|
/// Versione bulk: ricerca in un colpo solo tutte le associazioni attive per la combinazione
|
|
/// (KeyValue ∈ keyValues, DestinationEntity, RestCredentialName) usando una query SQL IN(...).
|
|
/// Riduce drasticamente le query SQLite quando si processano molti record.
|
|
/// </summary>
|
|
Task<Dictionary<string, KeyAssociation>> FindAssociationsByKeyValuesBulkAsync(
|
|
IEnumerable<string> keyValues,
|
|
string destinationEntity,
|
|
string restCredentialName);
|
|
|
|
/// <summary>
|
|
/// Versione thread-safe per operazioni parallele - Elimina associazione
|
|
/// </summary>
|
|
Task<bool> DeleteAssociationParallelAsync(int id);
|
|
|
|
/// <summary>
|
|
/// Marca le associazioni come cancellate dalla sorgente se i loro KeyValue non sono presenti nella lista fornita
|
|
/// </summary>
|
|
/// <param name="sourceKeyValues">Lista dei KeyValue attualmente presenti nella sorgente</param>
|
|
/// <param name="destinationEntity">Entità di destinazione</param>
|
|
/// <param name="restCredentialName">Nome della credenziale REST</param>
|
|
/// <returns>Numero di associazioni marcate come cancellate</returns>
|
|
Task<int> MarkDeletedAssociationsAsync(List<string> sourceKeyValues, string destinationEntity, string restCredentialName);
|
|
|
|
/// <summary>
|
|
/// Ottiene tutte le associazioni marcate come cancellate dalla sorgente ma non ancora sincronizzate
|
|
/// </summary>
|
|
Task<List<KeyAssociation>> GetPendingDeletionsAsync(string destinationEntity, string restCredentialName);
|
|
|
|
/// <summary>
|
|
/// Marca una cancellazione come sincronizzata
|
|
/// </summary>
|
|
Task<bool> MarkDeletionSyncedAsync(int associationId);
|
|
|
|
/// <summary>
|
|
/// Ottiene tutte le associazioni marcate come cancellate
|
|
/// </summary>
|
|
Task<List<KeyAssociation>> GetDeletedAssociationsAsync(string destinationEntity, string restCredentialName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Statistiche sulle associazioni
|
|
/// </summary>
|
|
public class AssociationStatistics
|
|
{
|
|
public int TotalAssociations { get; set; }
|
|
public int ActiveAssociations { get; set; }
|
|
public int InactiveAssociations { get; set; }
|
|
public int UniqueKeyValues { get; set; }
|
|
public int UniqueDestinationEntities { get; set; }
|
|
public DateTime? OldestAssociation { get; set; }
|
|
public DateTime? NewestAssociation { get; set; }
|
|
public Dictionary<string, int> AssociationsByEntity { get; set; } = new();
|
|
}
|