d042863a56
- Aggiunto supporto schedulazione con intervalli flessibili (secondi/minuti/ore/giorni/settimane/mesi) - Esteso modello ProfileSchedule con campi IntervalValue e IntervalUnit - Ottimizzato ScheduledJobService per controlli ogni 30s con esecuzione parallela - Implementata interfaccia UI completa con anteprima real-time in italiano - Aggiunta migrazione database AddIntervalSchedulingFields - Implementati metodi calcolo NextExecutionTime per intervalli - Aggiunta gestione tracking anti-duplicati e cleanup automatico - Creata documentazione completa (6 file, 2500+ righe) Modifiche tecniche: - ProfileSchedule.cs: Nuovi campi e metodi CalculateNextInterval/GetScheduleDescription - ScheduledJobService.cs: Ridotto check interval a 30s, aggiunto parallel processing - ProfileScheduleService.cs: Supporto calcolo intervalli in UpdateNextExecutionTimeAsync - Scheduling.razor: Aggiunta sezione UI per configurazione intervalli - Scheduling.razor.cs: Implementato GetIntervalPreview() e gestione stato campi
379 lines
14 KiB
C#
379 lines
14 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...");
|
|
|
|
// Verifica se il database esiste
|
|
var databaseExists = await _context.Database.CanConnectAsync();
|
|
|
|
if (!databaseExists)
|
|
{
|
|
_logger.LogInformation("Database non esistente, creazione in corso...");
|
|
|
|
// Applica tutte le migrazioni per creare il database con la struttura più recente
|
|
await _context.Database.MigrateAsync();
|
|
|
|
_logger.LogInformation("Database creato con tutte le migrazioni applicate");
|
|
|
|
// Aggiungi dati di esempio se necessario
|
|
await SeedInitialDataAsync();
|
|
}
|
|
else
|
|
{
|
|
_logger.LogInformation("Database esistente trovato");
|
|
|
|
// Applica eventuali migrazioni pendenti
|
|
var pendingMigrations = await _context.Database.GetPendingMigrationsAsync();
|
|
if (pendingMigrations.Any())
|
|
{
|
|
_logger.LogInformation("Trovate {Count} migrazioni pendenti: {Migrations}",
|
|
pendingMigrations.Count(), string.Join(", ", pendingMigrations));
|
|
|
|
try
|
|
{
|
|
await _context.Database.MigrateAsync();
|
|
_logger.LogInformation("Migrazioni applicate con successo");
|
|
}
|
|
catch (InvalidOperationException ex) when (ex.Message.Contains("PendingModelChangesWarning"))
|
|
{
|
|
_logger.LogWarning("Rilevate modifiche al modello pendenti, procedo con la creazione delle tabelle mancanti...");
|
|
|
|
// Creiamo le tabelle mancanti manualmente
|
|
await CreateScheduleExecutionHistoriesTableAsync();
|
|
await VerifyAndAddMissingColumnsAsync();
|
|
|
|
_logger.LogInformation("Tabelle e colonne mancanti create con successo");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_logger.LogInformation("Nessuna migrazione pendente");
|
|
}
|
|
|
|
// Verifica che le tabelle esistano
|
|
await VerifyTablesAsync();
|
|
}
|
|
}
|
|
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
|
|
try
|
|
{
|
|
await _context.KeyAssociations.CountAsync();
|
|
_logger.LogInformation("Tabella KeyAssociations verificata con successo");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogWarning(ex, "Tabella KeyAssociations non accessibile");
|
|
}
|
|
|
|
// Verifica se la tabella DataCouplerProfiles esiste
|
|
try
|
|
{
|
|
await _context.DataCouplerProfiles.CountAsync();
|
|
_logger.LogInformation("Tabella DataCouplerProfiles verificata con successo");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogWarning(ex, "Tabella DataCouplerProfiles non accessibile");
|
|
}
|
|
|
|
// Verifica se la tabella ProfileSchedules esiste
|
|
try
|
|
{
|
|
await _context.ProfileSchedules.CountAsync();
|
|
_logger.LogInformation("Tabella ProfileSchedules verificata con successo");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogWarning(ex, "Tabella ProfileSchedules non accessibile");
|
|
}
|
|
|
|
// Verifica se la tabella ScheduleExecutionHistories esiste
|
|
try
|
|
{
|
|
await _context.ScheduleExecutionHistories.CountAsync();
|
|
_logger.LogInformation("Tabella ScheduleExecutionHistories verificata con successo");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogWarning(ex, "Tabella ScheduleExecutionHistories non accessibile, tentativo di creazione...");
|
|
await CreateScheduleExecutionHistoriesTableAsync();
|
|
}
|
|
|
|
// Verifica e aggiungi colonne mancanti per ProfileSchedules
|
|
await VerifyAndAddMissingColumnsAsync();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Errore nella verifica delle tabelle");
|
|
|
|
// Se ci sono problemi gravi, prova a riapplicare le migrazioni
|
|
_logger.LogInformation("Tentativo di riapplicazione delle migrazioni...");
|
|
await _context.Database.MigrateAsync();
|
|
}
|
|
}
|
|
|
|
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 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;
|
|
}
|
|
}
|
|
|
|
private async Task CreateScheduleExecutionHistoriesTableAsync()
|
|
{
|
|
try
|
|
{
|
|
_logger.LogInformation("Creazione tabella ScheduleExecutionHistories...");
|
|
|
|
// Crea la tabella ScheduleExecutionHistories
|
|
await _context.Database.ExecuteSqlRawAsync(@"
|
|
CREATE TABLE IF NOT EXISTS ScheduleExecutionHistories (
|
|
Id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
ScheduleId INTEGER NOT NULL,
|
|
ProfileId INTEGER NOT NULL,
|
|
ProfileName TEXT NOT NULL,
|
|
StartTime DATETIME NOT NULL,
|
|
EndTime DATETIME,
|
|
Status TEXT NOT NULL,
|
|
Message TEXT,
|
|
RecordsProcessed INTEGER DEFAULT 0,
|
|
RecordsWithErrors INTEGER,
|
|
ErrorDetails TEXT,
|
|
TriggerType TEXT NOT NULL,
|
|
TriggeredBy TEXT,
|
|
SourceType TEXT,
|
|
DestinationType TEXT,
|
|
SourceInfo TEXT,
|
|
DestinationInfo TEXT,
|
|
AdditionalInfo TEXT,
|
|
CreatedAt DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (ScheduleId) REFERENCES ProfileSchedules (Id) ON DELETE CASCADE
|
|
)");
|
|
|
|
// Crea gli indici
|
|
await _context.Database.ExecuteSqlRawAsync(@"
|
|
CREATE INDEX IF NOT EXISTS IDX_ScheduleExecutionHistories_ScheduleId
|
|
ON ScheduleExecutionHistories (ScheduleId)");
|
|
|
|
await _context.Database.ExecuteSqlRawAsync(@"
|
|
CREATE INDEX IF NOT EXISTS IDX_ScheduleExecutionHistories_ProfileId
|
|
ON ScheduleExecutionHistories (ProfileId)");
|
|
|
|
await _context.Database.ExecuteSqlRawAsync(@"
|
|
CREATE INDEX IF NOT EXISTS IDX_ScheduleExecutionHistories_Status
|
|
ON ScheduleExecutionHistories (Status)");
|
|
|
|
await _context.Database.ExecuteSqlRawAsync(@"
|
|
CREATE INDEX IF NOT EXISTS IDX_ScheduleExecutionHistories_StartTime
|
|
ON ScheduleExecutionHistories (StartTime)");
|
|
|
|
await _context.Database.ExecuteSqlRawAsync(@"
|
|
CREATE INDEX IF NOT EXISTS IDX_ScheduleExecutionHistories_TriggerType
|
|
ON ScheduleExecutionHistories (TriggerType)");
|
|
|
|
_logger.LogInformation("Tabella ScheduleExecutionHistories creata con successo");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Errore durante la creazione della tabella ScheduleExecutionHistories");
|
|
_logger.LogWarning("Continuazione normale - la tabella potrebbe già esistere");
|
|
}
|
|
}
|
|
|
|
private async Task VerifyAndAddMissingColumnsAsync()
|
|
{
|
|
try
|
|
{
|
|
_logger.LogInformation("Verifica colonne mancanti per ProfileSchedules...");
|
|
|
|
// Verifica se le colonne di override database esistono
|
|
try
|
|
{
|
|
await _context.Database.ExecuteSqlRawAsync(
|
|
"SELECT SourceDatabaseOverride FROM ProfileSchedules LIMIT 1");
|
|
}
|
|
catch
|
|
{
|
|
_logger.LogInformation("Aggiunta colonna SourceDatabaseOverride...");
|
|
await _context.Database.ExecuteSqlRawAsync(
|
|
"ALTER TABLE ProfileSchedules ADD COLUMN SourceDatabaseOverride TEXT");
|
|
}
|
|
|
|
try
|
|
{
|
|
await _context.Database.ExecuteSqlRawAsync(
|
|
"SELECT DestinationDatabaseOverride FROM ProfileSchedules LIMIT 1");
|
|
}
|
|
catch
|
|
{
|
|
_logger.LogInformation("Aggiunta colonna DestinationDatabaseOverride...");
|
|
await _context.Database.ExecuteSqlRawAsync(
|
|
"ALTER TABLE ProfileSchedules ADD COLUMN DestinationDatabaseOverride TEXT");
|
|
}
|
|
|
|
_logger.LogInformation("Verifica colonne completata");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Errore durante la verifica/aggiunta colonne");
|
|
_logger.LogWarning("Continuazione normale - le colonne potrebbero già esistere");
|
|
}
|
|
}
|
|
}
|