fix: Correzione salvataggio campo MappedDestinationField in KeyAssociations
- Aggiunto campo MappedDestinationField al modello KeyAssociation per tracciare il campo destinazione mappato alla chiave sorgente
- Creata migration AddMappedDestinationFieldToKeyAssociation per aggiungere la colonna al database
- Implementata logica di popolamento in CreateAssociationAsync e StartDataTransferOriginal per salvare il campo destinazione mappato
- Aggiornato SaveAssociationParallelAsync per includere MappedDestinationField nelle query SQL UPDATE e INSERT
- Corretti indici parametri nella query UPDATE (da {7-9} a {8-10}) per includere il nuovo campo
- Aggiunta visualizzazione campo nell'interfaccia KeyAssociations (tabella, dettagli, export CSV)
- Implementato controllo validazione per impedire trasferimenti se il campo chiave non è mappato
- Aggiunto logging diagnostico dettagliato per debug del mapping dei campi
- Aggiornato ScheduledProfileExecutionService per popolare MappedDestinationField nelle esecuzioni schedulate
- Rimosso file BackgroundServices.cs obsoleto
- Documentazione completa creata (4 markdown files)
Fixes: Campo MappedDestinationField rimaneva NULL perché le query SQL raw non includevano il nuovo campo
This commit is contained in:
@@ -0,0 +1,279 @@
|
||||
# Fix MappedDestinationField Non Salvato nel Database
|
||||
|
||||
## 🐛 Problema Identificato
|
||||
|
||||
Il campo `MappedDestinationField` veniva popolato correttamente nell'oggetto `KeyAssociation` ma **non veniva scritto nel database**.
|
||||
|
||||
### Causa Root
|
||||
|
||||
Il metodo `SaveAssociationParallelAsync` in `KeyAssociationService.cs` utilizzava query SQL raw che **non includevano** il campo `MappedDestinationField` né nell'UPDATE né nell'INSERT.
|
||||
|
||||
## ✅ Correzione Implementata
|
||||
|
||||
### File Modificato
|
||||
|
||||
**`CredentialManager/Services/KeyAssociationService.cs`**
|
||||
|
||||
#### Metodo: `SaveAssociationParallelAsync` (linea ~159)
|
||||
|
||||
### 1. Aggiunta Cattura del Valore
|
||||
|
||||
```csharp
|
||||
var mappedDestinationField = association.MappedDestinationField; // AGGIUNTO
|
||||
```
|
||||
|
||||
### 2. Query UPDATE Corretta
|
||||
|
||||
**Prima:**
|
||||
```sql
|
||||
UPDATE KeyAssociations
|
||||
SET DestinationId = {0},
|
||||
SourceKeyField = {1},
|
||||
DestinationKeyField = {2},
|
||||
UpdatedAt = {3},
|
||||
LastVerifiedAt = {4},
|
||||
AdditionalInfo = {5},
|
||||
Data_Hash = {6}
|
||||
WHERE KeyValue = {7}
|
||||
AND DestinationEntity = {8}
|
||||
AND RestCredentialName = {9}
|
||||
AND IsActive = 1
|
||||
```
|
||||
|
||||
**Dopo:**
|
||||
```sql
|
||||
UPDATE KeyAssociations
|
||||
SET DestinationId = {0},
|
||||
SourceKeyField = {1},
|
||||
DestinationKeyField = {2},
|
||||
UpdatedAt = {3},
|
||||
LastVerifiedAt = {4},
|
||||
AdditionalInfo = {5},
|
||||
Data_Hash = {6},
|
||||
MappedDestinationField = {7} ← AGGIUNTO
|
||||
WHERE KeyValue = {8} ← Indici aggiornati
|
||||
AND DestinationEntity = {9} ← Indici aggiornati
|
||||
AND RestCredentialName = {10} ← Indici aggiornati
|
||||
AND IsActive = 1
|
||||
```
|
||||
|
||||
**Parametri aggiornati:**
|
||||
```csharp
|
||||
destinationId,
|
||||
sourceKeyField,
|
||||
destinationKeyField,
|
||||
currentTime,
|
||||
currentTime,
|
||||
additionalInfo ?? (object)DBNull.Value,
|
||||
dataHash ?? (object)DBNull.Value,
|
||||
mappedDestinationField ?? (object)DBNull.Value, // AGGIUNTO
|
||||
keyValue,
|
||||
destinationEntity,
|
||||
restCredentialName
|
||||
```
|
||||
|
||||
### 3. INSERT Corretto (Entity Framework)
|
||||
|
||||
**Prima:**
|
||||
```csharp
|
||||
var newAssociation = new KeyAssociation
|
||||
{
|
||||
KeyValue = keyValue,
|
||||
SourceKeyField = sourceKeyField,
|
||||
DestinationKeyField = destinationKeyField,
|
||||
// MappedDestinationField mancante!
|
||||
DestinationEntity = destinationEntity,
|
||||
DestinationId = destinationId,
|
||||
// ...
|
||||
};
|
||||
```
|
||||
|
||||
**Dopo:**
|
||||
```csharp
|
||||
var newAssociation = new KeyAssociation
|
||||
{
|
||||
KeyValue = keyValue,
|
||||
SourceKeyField = sourceKeyField,
|
||||
DestinationKeyField = destinationKeyField,
|
||||
MappedDestinationField = mappedDestinationField, // AGGIUNTO
|
||||
DestinationEntity = destinationEntity,
|
||||
DestinationId = destinationId,
|
||||
// ...
|
||||
};
|
||||
```
|
||||
|
||||
### 4. Logging Migliorato
|
||||
|
||||
**Prima:**
|
||||
```csharp
|
||||
_logger.LogDebug("PARALLEL: Tentativo salvataggio associazione - KeyValue: '{KeyValue}', DestinationEntity: '{DestinationEntity}', DestinationId: '{DestinationId}', RestCredentialName: '{RestCredentialName}'",
|
||||
keyValue, destinationEntity, destinationId, restCredentialName);
|
||||
```
|
||||
|
||||
**Dopo:**
|
||||
```csharp
|
||||
_logger.LogDebug("PARALLEL: Tentativo salvataggio associazione - KeyValue: '{KeyValue}', DestinationEntity: '{DestinationEntity}', DestinationId: '{DestinationId}', RestCredentialName: '{RestCredentialName}', MappedField: '{MappedField}'",
|
||||
keyValue, destinationEntity, destinationId, restCredentialName, mappedDestinationField ?? "NULL");
|
||||
|
||||
// E per il log di creazione:
|
||||
_logger.LogDebug("PARALLEL: Nuova associazione creata: KeyValue={KeyValue} -> {DestinationEntity}/{DestinationId}, MappedField={MappedField}",
|
||||
keyValue, destinationEntity, destinationId, mappedDestinationField ?? "NULL");
|
||||
```
|
||||
|
||||
## 🧪 Testing
|
||||
|
||||
### Procedura di Test
|
||||
|
||||
1. **Fermare l'applicazione** attualmente in esecuzione
|
||||
|
||||
2. **Ricompilare il progetto**:
|
||||
```powershell
|
||||
dotnet build Data_Coupler.sln
|
||||
```
|
||||
|
||||
3. **Riavviare l'applicazione**
|
||||
|
||||
4. **Eseguire un nuovo trasferimento dati**:
|
||||
- Configurare un mapping (es: Email → EmailAddress)
|
||||
- Selezionare "Email" come campo chiave
|
||||
- Eseguire il trasferimento
|
||||
|
||||
5. **Verificare nei log**:
|
||||
```
|
||||
PARALLEL: Tentativo salvataggio associazione - ... MappedField: 'EmailAddress'
|
||||
PARALLEL: Nuova associazione creata: ... MappedField: EmailAddress
|
||||
```
|
||||
|
||||
6. **Verificare nel database**:
|
||||
```sql
|
||||
SELECT
|
||||
Id,
|
||||
KeyValue,
|
||||
SourceKeyField,
|
||||
MappedDestinationField, -- Questo campo ora deve essere popolato!
|
||||
DestinationKeyField,
|
||||
DestinationId
|
||||
FROM KeyAssociations
|
||||
ORDER BY CreatedAt DESC
|
||||
LIMIT 10;
|
||||
```
|
||||
|
||||
### Risultato Atteso
|
||||
|
||||
**Prima del Fix:**
|
||||
| Id | KeyValue | SourceKeyField | MappedDestinationField | DestinationKeyField | DestinationId |
|
||||
|----|----------|----------------|------------------------|---------------------|---------------|
|
||||
| 1 | C00001 | CardCode | NULL ❌ | Id | ABC123 |
|
||||
| 2 | C00026 | CardCode | NULL ❌ | Id | DEF456 |
|
||||
|
||||
**Dopo il Fix:**
|
||||
| Id | KeyValue | SourceKeyField | MappedDestinationField | DestinationKeyField | DestinationId |
|
||||
|----|----------|----------------|------------------------|---------------------|---------------|
|
||||
| 1 | C00001 | CardCode | **EmailAddress** ✅ | Id | ABC123 |
|
||||
| 2 | C00026 | CardCode | **EmailAddress** ✅ | Id | DEF456 |
|
||||
|
||||
## 📊 Impatto della Correzione
|
||||
|
||||
### Componenti Affetti
|
||||
|
||||
- ✅ **Creazione nuove associazioni**: Ora salva correttamente il campo
|
||||
- ✅ **Aggiornamento associazioni esistenti**: Ora aggiorna correttamente il campo
|
||||
- ✅ **Logging**: Ora mostra il valore nel log per debug
|
||||
- ✅ **UI KeyAssociations**: Ora mostrerà il valore invece di "N/A"
|
||||
|
||||
### Retrocompatibilità
|
||||
|
||||
✅ **Completamente compatibile**:
|
||||
- Le associazioni esistenti (con campo NULL) continueranno a funzionare
|
||||
- Le nuove associazioni avranno il campo popolato
|
||||
- Nessuna migration aggiuntiva richiesta (il campo è già nel database)
|
||||
|
||||
## 🔍 Spiegazione Tecnica
|
||||
|
||||
### Perché il Campo Non Veniva Salvato?
|
||||
|
||||
Il metodo `SaveAssociationParallelAsync` usa un pattern di **upsert ottimizzato** per gestire race conditions in operazioni parallele:
|
||||
|
||||
1. **Primo tentativo**: UPDATE via SQL raw
|
||||
2. **Se fallisce**: INSERT via Entity Framework
|
||||
|
||||
Il problema era che:
|
||||
- ❌ La query SQL raw dell'UPDATE **non includeva** `MappedDestinationField`
|
||||
- ❌ L'oggetto Entity Framework dell'INSERT **non assegnava** `MappedDestinationField`
|
||||
|
||||
### Perché Usare SQL Raw?
|
||||
|
||||
```csharp
|
||||
// SQL Raw per UPDATE (più performante e thread-safe)
|
||||
await parallelContext.Database.ExecuteSqlRawAsync(@"UPDATE ...");
|
||||
|
||||
// Entity Framework per INSERT (più semplice per gestire race conditions)
|
||||
parallelContext.KeyAssociations.Add(newAssociation);
|
||||
await parallelContext.SaveChangesAsync();
|
||||
```
|
||||
|
||||
**Vantaggi**:
|
||||
- Performance migliori per UPDATE
|
||||
- Gestione automatica race conditions per INSERT
|
||||
- Thread-safe con DbContext separati
|
||||
|
||||
## ✅ Checklist Verifica
|
||||
|
||||
- [x] Campo aggiunto alla query UPDATE SQL
|
||||
- [x] Campo aggiunto all'oggetto INSERT Entity Framework
|
||||
- [x] Parametri query SQL aggiornati con indici corretti
|
||||
- [x] Logging aggiornato per includere MappedDestinationField
|
||||
- [x] Verifica assenza errori di compilazione
|
||||
- [ ] **Fermare applicazione**
|
||||
- [ ] **Ricompilare progetto**
|
||||
- [ ] **Riavviare applicazione**
|
||||
- [ ] **Eseguire test trasferimento**
|
||||
- [ ] **Verificare log contiene "MappedField: 'XXX'"**
|
||||
- [ ] **Verificare database con SELECT**
|
||||
|
||||
## 🎯 Prossimi Passi IMMEDIATI
|
||||
|
||||
1. ⛔ **FERMARE** l'applicazione in esecuzione
|
||||
2. 🔨 **Ricompilare**:
|
||||
```powershell
|
||||
dotnet build Data_Coupler.sln
|
||||
```
|
||||
3. ▶️ **Riavviare** l'applicazione
|
||||
4. 🧪 **Test completo**:
|
||||
- Crea nuovo mapping con campo chiave
|
||||
- Esegui trasferimento
|
||||
- Verifica log: `"MappedField: 'EmailAddress'"`
|
||||
- Query database per conferma
|
||||
5. 🗑️ **Opzionale**: Cancella vecchie associazioni di test (con campo NULL)
|
||||
|
||||
## 📝 Note Aggiuntive
|
||||
|
||||
### Cancellare Associazioni Vecchie (Opzionale)
|
||||
|
||||
Se vuoi pulire le associazioni di test create prima del fix:
|
||||
|
||||
```sql
|
||||
-- Mostra associazioni con campo NULL
|
||||
SELECT * FROM KeyAssociations
|
||||
WHERE MappedDestinationField IS NULL;
|
||||
|
||||
-- Cancella associazioni di test (ATTENZIONE!)
|
||||
DELETE FROM KeyAssociations
|
||||
WHERE MappedDestinationField IS NULL
|
||||
AND CreatedAt > '2025-10-19'; -- Solo quelle create oggi
|
||||
```
|
||||
|
||||
### Verifica Migration Database
|
||||
|
||||
Se il database non ha la colonna, esegui:
|
||||
|
||||
```powershell
|
||||
cd CredentialManager
|
||||
dotnet ef database update
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Data Correzione**: 20 Ottobre 2025
|
||||
**Versione**: 3.0 - Fix Salvataggio Database
|
||||
**Status**: ✅ Pronto per test - Ricompilazione richiesta
|
||||
Reference in New Issue
Block a user