Files
Data-Coupler/Data_Coupler/Pages/TestAssociations.razor
T
Alessio 04f0403f12 feat: Implementato sistema di associazioni chiave per prevenire duplicati nel data coupling
BREAKING CHANGE: Rimosso completamente il vecchio sistema RecordAssociation

Modifiche principali:
- Sostituito RecordAssociation con KeyAssociation basato sui valori delle chiavi
- Implementata logica robusta di UPDATE vs INSERT basata su associazioni esistenti
- Aggiunta normalizzazione delle chiavi (.Trim()) per consistenza
- Implementato fallback nella ricerca associazioni per maggiore affidabilità
- Sostituita verifica pre-UPDATE con tentativo diretto più efficiente

Componenti modificati:
- Nuovo modello: KeyAssociation.cs con campi ottimizzati
- Nuovo servizio: KeyAssociationService.cs con metodi completi
- Aggiornato: DataCoupler.razor con logica migliorata di gestione associazioni
- Aggiornato: CredentialDbContext per gestire solo KeyAssociations
- Aggiornati: tutti i servizi di interfaccia per supportare il nuovo sistema
- Creata: pagina KeyAssociations.razor per gestione associazioni
- Aggiornato: NavMenu.razor con link alla gestione associazioni

Miglioramenti tecnici:
- Logica di UPDATE più robusta: tenta direttamente l'aggiornamento invece di verificare prima l'esistenza
- Gestione errori migliorata con cleanup automatico delle associazioni non valide
- Debug logging estensivo per troubleshooting
- Fallback nella ricerca associazioni se parametri specifici falliscono
- Normalizzazione valori chiave per prevenire problemi di whitespace

Risultato:
Il sistema ora previene correttamente i duplicati utilizzando le associazioni per decidere
se fare INSERT (nuovo record) o UPDATE (record esistente) basandosi sui valori delle chiavi.

Database:
- Creata migrazione EF per rimuovere RecordAssociations e aggiungere KeyAssociations
- Eliminati file e codice legacy non più necessari
2025-06-29 20:44:20 +02:00

317 lines
14 KiB
Plaintext

@page "/test-associations"
@using CredentialManager.Models
@using DataConnection.CredentialManagement.Interfaces
@inject IDataConnectionCredentialService CredentialService
@inject ILogger<TestAssociations> Logger
<PageTitle>Test Associazioni</PageTitle>
<div class="container-fluid">
<div class="row">
<div class="col-12">
<h3><i class="fas fa-vial"></i> Test Sistema Associazioni Chiave</h3>
<div class="row mt-4">
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h5><i class="fas fa-plus"></i> Crea Associazione Test</h5>
</div>
<div class="card-body">
<div class="mb-3">
<label class="form-label">Valore Chiave:</label>
<input type="text" class="form-control" @bind="testKeyValue" placeholder="es. 12345" />
</div>
<div class="mb-3">
<label class="form-label">Entità Destinazione:</label>
<input type="text" class="form-control" @bind="testDestinationEntity" placeholder="es. BusinessPartners" />
</div>
<div class="mb-3">
<label class="form-label">ID Destinazione:</label>
<input type="text" class="form-control" @bind="testDestinationId" placeholder="es. BP001" />
</div>
<div class="mb-3">
<label class="form-label">Credenziale REST:</label>
<input type="text" class="form-control" @bind="testRestCredential" placeholder="es. SAP_B1" />
</div>
<button class="btn btn-primary" @onclick="CreateTestAssociation">
<i class="fas fa-save"></i> Crea Associazione
</button>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h5><i class="fas fa-search"></i> Cerca Associazione</h5>
</div>
<div class="card-body">
<div class="mb-3">
<label class="form-label">Valore Chiave da Cercare:</label>
<input type="text" class="form-control" @bind="searchKeyValue" placeholder="es. 12345" />
</div>
<div class="mb-3">
<label class="form-label">Entità Destinazione:</label>
<input type="text" class="form-control" @bind="searchDestinationEntity" placeholder="es. BusinessPartners" />
</div>
<div class="mb-3">
<label class="form-label">Credenziale REST:</label>
<input type="text" class="form-control" @bind="searchRestCredential" placeholder="es. SAP_B1" />
</div>
<button class="btn btn-info" @onclick="SearchAssociation">
<i class="fas fa-search"></i> Cerca
</button>
</div>
</div>
</div>
</div>
@if (!string.IsNullOrEmpty(resultMessage))
{
<div class="alert @(resultType == "success" ? "alert-success" : resultType == "error" ? "alert-danger" : "alert-info") mt-4">
<i class="fas @(resultType == "success" ? "fa-check-circle" : resultType == "error" ? "fa-exclamation-triangle" : "fa-info-circle")"></i>
@resultMessage
</div>
}
@if (foundAssociation != null)
{
<div class="card mt-4">
<div class="card-header">
<h5><i class="fas fa-link"></i> Associazione Trovata</h5>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<strong>ID:</strong> @foundAssociation.Id<br>
<strong>Valore Chiave:</strong> @foundAssociation.KeyValue<br>
<strong>Campo Chiave Sorgente:</strong> @foundAssociation.SourceKeyField<br>
<strong>Campo Chiave Destinazione:</strong> @foundAssociation.DestinationKeyField<br>
</div>
<div class="col-md-6">
<strong>Entità Destinazione:</strong> @foundAssociation.DestinationEntity<br>
<strong>ID Destinazione:</strong> @foundAssociation.DestinationId<br>
<strong>Credenziale REST:</strong> @foundAssociation.RestCredentialName<br>
<strong>Attiva:</strong> @(foundAssociation.IsActive ? "Sì" : "No")<br>
</div>
</div>
<div class="row mt-2">
<div class="col-12">
<strong>Creata:</strong> @foundAssociation.CreatedAt.ToString("dd/MM/yyyy HH:mm:ss")<br>
@if (foundAssociation.UpdatedAt.HasValue)
{
<strong>Aggiornata:</strong> @foundAssociation.UpdatedAt.Value.ToString("dd/MM/yyyy HH:mm:ss")<br>
}
@if (foundAssociation.LastVerifiedAt.HasValue)
{
<strong>Ultima Verifica:</strong> @foundAssociation.LastVerifiedAt.Value.ToString("dd/MM/yyyy HH:mm:ss")<br>
}
</div>
</div>
</div>
</div>
}
<div class="card mt-4">
<div class="card-header">
<h5><i class="fas fa-list"></i> Tutte le Associazioni Attive</h5>
</div>
<div class="card-body">
<button class="btn btn-secondary mb-3" @onclick="LoadAllAssociations">
<i class="fas fa-refresh"></i> Carica Associazioni
</button>
@if (allAssociations != null && allAssociations.Any())
{
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th>ID</th>
<th>Valore Chiave</th>
<th>Entità</th>
<th>ID Destinazione</th>
<th>Credenziale</th>
<th>Creata</th>
<th>Azioni</th>
</tr>
</thead>
<tbody>
@foreach (var assoc in allAssociations)
{
<tr>
<td>@assoc.Id</td>
<td>@assoc.KeyValue</td>
<td>@assoc.DestinationEntity</td>
<td>@assoc.DestinationId</td>
<td>@assoc.RestCredentialName</td>
<td>@assoc.CreatedAt.ToString("dd/MM/yyyy HH:mm")</td>
<td>
<button class="btn btn-sm btn-danger" @onclick="() => DeleteAssociation(assoc.Id)">
<i class="fas fa-trash"></i>
</button>
</td>
</tr>
}
</tbody>
</table>
</div>
}
else if (allAssociations != null)
{
<div class="alert alert-info">
<i class="fas fa-info-circle"></i> Nessuna associazione trovata.
</div>
}
</div>
</div>
</div>
</div>
</div>
@code {
private string testKeyValue = "";
private string testDestinationEntity = "";
private string testDestinationId = "";
private string testRestCredential = "";
private string searchKeyValue = "";
private string searchDestinationEntity = "";
private string searchRestCredential = "";
private string resultMessage = "";
private string resultType = "";
private KeyAssociation? foundAssociation;
private List<KeyAssociation>? allAssociations;
private async Task CreateTestAssociation()
{
try
{
if (string.IsNullOrEmpty(testKeyValue) || string.IsNullOrEmpty(testDestinationEntity) ||
string.IsNullOrEmpty(testDestinationId) || string.IsNullOrEmpty(testRestCredential))
{
resultMessage = "Tutti i campi sono obbligatori.";
resultType = "error";
return;
}
var association = new KeyAssociation
{
KeyValue = testKeyValue,
SourceKeyField = "test_field",
DestinationKeyField = "id",
DestinationEntity = testDestinationEntity,
DestinationId = testDestinationId,
RestCredentialName = testRestCredential,
CreatedAt = DateTime.UtcNow,
LastVerifiedAt = DateTime.UtcNow,
AdditionalInfo = "{\"test\": true}"
};
var id = await CredentialService.SaveKeyAssociationAsync(association);
resultMessage = $"Associazione creata con successo! ID: {id}";
resultType = "success";
Logger.LogInformation("Associazione test creata: ID={Id}, KeyValue={KeyValue}", id, testKeyValue);
}
catch (Exception ex)
{
resultMessage = $"Errore nella creazione: {ex.Message}";
resultType = "error";
Logger.LogError(ex, "Errore nella creazione dell'associazione test");
}
}
private async Task SearchAssociation()
{
try
{
if (string.IsNullOrEmpty(searchKeyValue))
{
resultMessage = "Valore chiave obbligatorio per la ricerca.";
resultType = "error";
return;
}
foundAssociation = null;
if (!string.IsNullOrEmpty(searchDestinationEntity) && !string.IsNullOrEmpty(searchRestCredential))
{
foundAssociation = await CredentialService.FindKeyAssociationByValueAsync(
searchKeyValue, searchDestinationEntity, searchRestCredential);
}
else
{
foundAssociation = await CredentialService.FindKeyAssociationByValueAsync(searchKeyValue);
}
if (foundAssociation != null)
{
resultMessage = "Associazione trovata!";
resultType = "success";
Logger.LogInformation("Associazione trovata: ID={Id} per KeyValue={KeyValue}", foundAssociation.Id, searchKeyValue);
}
else
{
resultMessage = "Nessuna associazione trovata con i criteri specificati.";
resultType = "info";
Logger.LogInformation("Nessuna associazione trovata per KeyValue={KeyValue}", searchKeyValue);
}
}
catch (Exception ex)
{
resultMessage = $"Errore nella ricerca: {ex.Message}";
resultType = "error";
Logger.LogError(ex, "Errore nella ricerca dell'associazione");
}
}
private async Task LoadAllAssociations()
{
try
{
allAssociations = await CredentialService.GetAllActiveKeyAssociationsAsync();
resultMessage = $"Caricate {allAssociations.Count} associazioni attive.";
resultType = "info";
}
catch (Exception ex)
{
resultMessage = $"Errore nel caricamento: {ex.Message}";
resultType = "error";
Logger.LogError(ex, "Errore nel caricamento delle associazioni");
}
}
private async Task DeleteAssociation(int id)
{
try
{
var result = await CredentialService.DeleteKeyAssociationAsync(id);
if (result)
{
resultMessage = $"Associazione {id} eliminata con successo.";
resultType = "success";
await LoadAllAssociations(); // Ricarica la lista
}
else
{
resultMessage = $"Errore nell'eliminazione dell'associazione {id}.";
resultType = "error";
}
}
catch (Exception ex)
{
resultMessage = $"Errore nell'eliminazione: {ex.Message}";
resultType = "error";
Logger.LogError(ex, "Errore nell'eliminazione dell'associazione {Id}", id);
}
}
protected override async Task OnInitializedAsync()
{
await LoadAllAssociations();
}
}