Files
Data-Coupler/CredentialManager/Services/DatabaseInitializer.cs
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

319 lines
12 KiB
C#

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using CredentialManager.Data;
namespace CredentialManager.Services;
/// <summary>
/// Interfaccia per l'inizializzazione del database
/// </summary>
public interface IDatabaseInitializer
{
Task InitializeAsync();
Task<bool> DatabaseExistsAsync();
}
/// <summary>
/// Servizio per l'inizializzazione del database SQLite
/// </summary>
public class DatabaseInitializer : IDatabaseInitializer
{
private readonly CredentialDbContext _context;
private readonly ILogger<DatabaseInitializer> _logger;
public DatabaseInitializer(CredentialDbContext context, ILogger<DatabaseInitializer> logger)
{
_context = context;
_logger = logger;
} public async Task InitializeAsync()
{
try
{
_logger.LogInformation("Inizializzazione database in corso...");
// Per SQLite con EnsureCreatedAsync è più semplice e sicuro
// Crea il database e le tabelle se non esistono
var created = await _context.Database.EnsureCreatedAsync();
if (created)
{
_logger.LogInformation("Database creato con successo");
// Aggiungi dati di esempio se necessario
await SeedInitialDataAsync();
}
else
{
_logger.LogInformation("Database esistente trovato");
// Verifica che le tabelle esistano
await VerifyTablesAsync();
// Applica migrazioni se necessario
await ApplyMigrationsAsync();
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore durante l'inizializzazione del database");
throw;
}
}
private async Task VerifyTablesAsync()
{
try
{
// Verifica che la tabella principale Credentials esista
await _context.Credentials.CountAsync();
_logger.LogInformation("Tabella Credentials verificata con successo");
// Verifica se la tabella KeyAssociations esiste, se non esiste la crea senza ricreare tutto il database
try
{
await _context.KeyAssociations.CountAsync();
_logger.LogInformation("Tabella KeyAssociations verificata con successo");
}
catch (Exception)
{
_logger.LogInformation("Tabella KeyAssociations non trovata, creazione tramite migrazione...");
await CreateKeyAssociationsTableAsync();
}
}
catch (Exception)
{
_logger.LogWarning("Tabella Credentials mancante, ricreazione database...");
// Solo se la tabella principale non esiste, ricreiamo tutto
await _context.Database.EnsureDeletedAsync();
await _context.Database.EnsureCreatedAsync();
await SeedInitialDataAsync();
_logger.LogInformation("Database ricreato con successo");
}
}
public async Task<bool> DatabaseExistsAsync()
{
try
{
return await _context.Database.CanConnectAsync();
}
catch
{
return false;
}
}
private async Task SeedInitialDataAsync()
{
try
{
// Verifica se ci sono già dati
if (await _context.Credentials.AnyAsync())
{
_logger.LogInformation("Dati esistenti trovati. Seeding saltato.");
return;
}
_logger.LogInformation("Aggiunta dati di esempio...");
// Qui puoi aggiungere dati di esempio se necessario
// Ad esempio:
/*
var sampleCredentials = new List<CredentialEntity>
{
new CredentialEntity
{
Name = "Esempio Database",
Type = "Database",
Host = "localhost",
Port = 1433,
DatabaseName = "TestDB",
Username = "testuser",
CreatedAt = DateTime.UtcNow,
CreatedBy = "System",
IsActive = true
}
};
_context.Credentials.AddRange(sampleCredentials);
await _context.SaveChangesAsync();
*/
_logger.LogInformation("Dati di esempio aggiunti con successo");
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore durante il seeding dei dati iniziali");
throw;
}
} private async Task ApplyMigrationsAsync()
{
try
{
_logger.LogInformation("Verifica e applicazione migrazioni...");
// Migrazione 1: Verifica se la colonna RestServiceType esiste
try
{
await _context.Database.ExecuteSqlRawAsync(
"SELECT RestServiceType FROM Credentials LIMIT 1");
_logger.LogInformation("Colonna RestServiceType già presente");
}
catch (Microsoft.Data.Sqlite.SqliteException)
{
// La colonna non esiste, la aggiungiamo
_logger.LogInformation("Aggiunta colonna RestServiceType...");
await _context.Database.ExecuteSqlRawAsync(
"ALTER TABLE Credentials ADD COLUMN RestServiceType TEXT");
_logger.LogInformation("Colonna RestServiceType aggiunta con successo");
}
// Migrazione 2: Elimina vecchia tabella RecordAssociations se esiste e crea KeyAssociations
try
{
// Prova a eliminare la vecchia tabella se esiste
await _context.Database.ExecuteSqlRawAsync("DROP TABLE IF EXISTS RecordAssociations");
_logger.LogInformation("Vecchia tabella RecordAssociations eliminata");
}
catch (Exception)
{
// Ignora errori se la tabella non esiste
}
// Verifica se la tabella KeyAssociations esiste
try
{
await _context.Database.ExecuteSqlRawAsync(
"SELECT COUNT(*) FROM KeyAssociations LIMIT 1");
_logger.LogInformation("Tabella KeyAssociations già presente");
}
catch (Microsoft.Data.Sqlite.SqliteException)
{
// La tabella non esiste, la creiamo
_logger.LogInformation("Creazione tabella KeyAssociations...");
// Crea la tabella
await _context.Database.ExecuteSqlRawAsync(@"
CREATE TABLE KeyAssociations (
Id INTEGER PRIMARY KEY AUTOINCREMENT,
KeyValue TEXT NOT NULL,
SourceKeyField TEXT NOT NULL,
DestinationKeyField TEXT NOT NULL,
DestinationEntity TEXT NOT NULL,
DestinationId TEXT NOT NULL,
RestCredentialName TEXT NOT NULL,
CreatedAt TEXT NOT NULL DEFAULT (datetime('now')),
UpdatedAt TEXT,
LastVerifiedAt TEXT,
IsActive INTEGER NOT NULL DEFAULT 1,
SourcesInfo TEXT,
AdditionalInfo TEXT
)");
// Crea gli indici
await _context.Database.ExecuteSqlRawAsync(@"
CREATE INDEX IX_KeyAssociations_KeyValue
ON KeyAssociations (KeyValue)");
await _context.Database.ExecuteSqlRawAsync(@"
CREATE UNIQUE INDEX IX_KeyAssociations_Unique
ON KeyAssociations (KeyValue, DestinationEntity, RestCredentialName)");
await _context.Database.ExecuteSqlRawAsync(@"
CREATE INDEX IX_KeyAssociations_DestinationEntity
ON KeyAssociations (DestinationEntity)");
await _context.Database.ExecuteSqlRawAsync(@"
CREATE INDEX IX_KeyAssociations_RestCredentialName
ON KeyAssociations (RestCredentialName)");
await _context.Database.ExecuteSqlRawAsync(@"
CREATE INDEX IX_KeyAssociations_IsActive
ON KeyAssociations (IsActive)");
await _context.Database.ExecuteSqlRawAsync(@"
CREATE INDEX IX_KeyAssociations_CreatedAt
ON KeyAssociations (CreatedAt)");
await _context.Database.ExecuteSqlRawAsync(@"
CREATE INDEX IX_KeyAssociations_LastVerifiedAt
ON KeyAssociations (LastVerifiedAt)");
_logger.LogInformation("Tabella KeyAssociations creata con successo");
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore nell'applicazione delle migrazioni");
throw;
}
}
private async Task CreateKeyAssociationsTableAsync()
{
try
{
_logger.LogInformation("Creazione tabella KeyAssociations...");
// Elimina la vecchia tabella se esiste
await _context.Database.ExecuteSqlRawAsync("DROP TABLE IF EXISTS RecordAssociations");
// Crea la nuova tabella
await _context.Database.ExecuteSqlRawAsync(@"
CREATE TABLE KeyAssociations (
Id INTEGER PRIMARY KEY AUTOINCREMENT,
KeyValue TEXT NOT NULL,
SourceKeyField TEXT NOT NULL,
DestinationKeyField TEXT NOT NULL,
DestinationEntity TEXT NOT NULL,
DestinationId TEXT NOT NULL,
RestCredentialName TEXT NOT NULL,
CreatedAt TEXT NOT NULL DEFAULT (datetime('now')),
UpdatedAt TEXT,
LastVerifiedAt TEXT,
IsActive INTEGER NOT NULL DEFAULT 1,
SourcesInfo TEXT,
AdditionalInfo TEXT
)");
// Crea gli indici
await _context.Database.ExecuteSqlRawAsync(@"
CREATE INDEX IX_KeyAssociations_KeyValue
ON KeyAssociations (KeyValue)");
await _context.Database.ExecuteSqlRawAsync(@"
CREATE UNIQUE INDEX IX_KeyAssociations_Unique
ON KeyAssociations (KeyValue, DestinationEntity, RestCredentialName)");
await _context.Database.ExecuteSqlRawAsync(@"
CREATE INDEX IX_KeyAssociations_DestinationEntity
ON KeyAssociations (DestinationEntity)");
await _context.Database.ExecuteSqlRawAsync(@"
CREATE INDEX IX_KeyAssociations_RestCredentialName
ON KeyAssociations (RestCredentialName)");
await _context.Database.ExecuteSqlRawAsync(@"
CREATE INDEX IX_KeyAssociations_IsActive
ON KeyAssociations (IsActive)");
await _context.Database.ExecuteSqlRawAsync(@"
CREATE INDEX IX_KeyAssociations_CreatedAt
ON KeyAssociations (CreatedAt)");
await _context.Database.ExecuteSqlRawAsync(@"
CREATE INDEX IX_KeyAssociations_LastVerifiedAt
ON KeyAssociations (LastVerifiedAt)");
_logger.LogInformation("Tabella KeyAssociations creata con successo");
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore nella creazione della tabella KeyAssociations");
throw;
}
}
}