5d9b9756cf
- 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
280 lines
8.3 KiB
Markdown
280 lines
8.3 KiB
Markdown
# 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
|