04f0403f12
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
319 lines
12 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|