feat: Implementazione completa sistema schedulazione con intervalli personalizzati

- 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
This commit is contained in:
2025-10-02 01:12:39 +02:00
parent b76a6760fb
commit d042863a56
71 changed files with 17860 additions and 144 deletions
+168 -46
View File
@@ -10,6 +10,7 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.Data.SqlClient;
using CredentialManager.Migrations;
namespace DataConnection.EF;
@@ -101,41 +102,87 @@ public class EFCoreDatabaseManager : IDatabaseManager
return await _context.Set<T>().FromSqlRaw(sql, parameters).ToListAsync();
}
public async Task<List<Dictionary<string, object>>> ExecuteRawQueryAsync(string sql, params object[] parameters)
public async Task<List<Dictionary<string, object>>> ExecuteRawQueryAsync(string sql, string databaseName, params object[] parameters)
{
using var command = _context.Database.GetDbConnection().CreateCommand();
command.CommandText = sql;
// Aggiungi i parametri
for (int i = 0; i < parameters.Length; i++)
try
{
var parameter = command.CreateParameter();
parameter.ParameterName = $"@p{i}";
parameter.Value = parameters[i] ?? DBNull.Value;
command.Parameters.Add(parameter);
}
await _context.Database.OpenConnectionAsync();
var results = new List<Dictionary<string, object>>();
using var reader = await command.ExecuteReaderAsync();
while (await reader.ReadAsync())
{
var row = new Dictionary<string, object>();
for (int i = 0; i < reader.FieldCount; i++)
// Assicurarsi che la connessione sia aperta
if (_context.Database.GetDbConnection().State != ConnectionState.Open)
{
var columnName = reader.GetName(i);
var value = reader.IsDBNull(i) ? null : reader.GetValue(i);
row[columnName] = value ?? ""; // Usa stringa vuota invece di null
await _context.Database.OpenConnectionAsync();
}
results.Add(row);
var connection = _context.Database.GetDbConnection();
// Debug: verifica il database attuale della connessione
Console.WriteLine($"ExecuteRawQueryAsync: Database connessione prima: {connection.Database}");
Console.WriteLine($"ExecuteRawQueryAsync: Connection string: {connection.ConnectionString}");
// Estrai il database target dalla connection string del DbContext
var connectionString = _context.Database.GetConnectionString();
Console.WriteLine($"ExecuteRawQueryAsync: Connection string completa: {connectionString}");
if (!string.IsNullOrEmpty(connectionString))
{
//var builder = new SqlConnectionStringBuilder(connectionString);
var targetDatabase = databaseName;
Console.WriteLine($"ExecuteRawQueryAsync: InitialCatalog dalla connection string: '{targetDatabase}'");
Console.WriteLine($"ExecuteRawQueryAsync: Database corrente connessione: '{connection.Database}'");
// Se il database della connessione non corrisponde al target, forza il cambio
if (!string.IsNullOrEmpty(targetDatabase) && !string.Equals(connection.Database, targetDatabase, StringComparison.OrdinalIgnoreCase))
{
Console.WriteLine($"ExecuteRawQueryAsync: MISMATCH RILEVATO! Forzando cambio database da '{connection.Database}' a '{targetDatabase}'");
await connection.ChangeDatabaseAsync(targetDatabase);
Console.WriteLine($"ExecuteRawQueryAsync: Database connessione dopo cambio: '{connection.Database}'");
}
else
{
Console.WriteLine($"ExecuteRawQueryAsync: Database già corretto: '{connection.Database}' = '{targetDatabase}'");
}
}
else
{
Console.WriteLine("ExecuteRawQueryAsync: ATTENZIONE! Connection string è vuota!");
}
using var command = connection.CreateCommand();
command.CommandText = sql;
// Aggiungi i parametri
for (int i = 0; i < parameters.Length; i++)
{
var parameter = command.CreateParameter();
parameter.ParameterName = $"@p{i}";
parameter.Value = parameters[i] ?? DBNull.Value;
command.Parameters.Add(parameter);
}
var results = new List<Dictionary<string, object>>();
using var reader = await command.ExecuteReaderAsync();
while (await reader.ReadAsync())
{
var row = new Dictionary<string, object>();
for (int i = 0; i < reader.FieldCount; i++)
{
var columnName = reader.GetName(i);
var value = reader.IsDBNull(i) ? null : reader.GetValue(i);
row[columnName] = value ?? ""; // Usa stringa vuota invece di null
}
results.Add(row);
}
return results;
}
catch (Exception ex)
{
Console.WriteLine($"Errore nell'esecuzione della query raw: {ex.Message}");
throw;
}
return results;
}
public async Task<int> ExecuteCommandAsync(string sql, params object[] parameters)
@@ -148,15 +195,20 @@ public class EFCoreDatabaseManager : IDatabaseManager
try
{
// Assicurarsi che il contesto sia connesso
await _context.Database.OpenConnectionAsync();
if (_context.Database.GetDbConnection().State != ConnectionState.Open)
{
await _context.Database.OpenConnectionAsync();
}
// Usa la factory per ottenere il provider appropriato in base al tipo di database
var schemaProvider = DatabaseSchemaProviderFactory.CreateProvider(_options.DatabaseType);
// Usa il provider per ottenere lo schema
// Usa il provider per ottenere lo schema utilizzando la connection string corrente del DbContext
var connectionString = _context.Database.GetConnectionString();
if (connectionString == null)
throw new InvalidOperationException("Connection string is null");
Console.WriteLine($"GetDatabaseSchemaAsync: Utilizzo connection string: {connectionString}");
var result = await schemaProvider.GetDatabaseSchemaAsync(connectionString);
@@ -180,7 +232,7 @@ public class EFCoreDatabaseManager : IDatabaseManager
// Usa la factory per ottenere il provider appropriato in base al tipo di database
var schemaProvider = DatabaseSchemaProviderFactory.CreateProvider(_options.DatabaseType);
// Usa il provider per ottenere la lista dei database
// Usa il provider per ottenere la lista dei database utilizzando la connection string corrente del DbContext
var connectionString = _context.Database.GetConnectionString();
if (connectionString == null)
throw new InvalidOperationException("Connection string is null");
@@ -207,7 +259,7 @@ public class EFCoreDatabaseManager : IDatabaseManager
// Usa la factory per ottenere il provider appropriato in base al tipo di database
var schemaProvider = DatabaseSchemaProviderFactory.CreateProvider(_options.DatabaseType);
// Usa il provider per ottenere la lista delle tabelle
// Usa il provider per ottenere la lista delle tabelle utilizzando la connection string corrente del DbContext
var connectionString = _context.Database.GetConnectionString();
if (connectionString == null)
throw new InvalidOperationException("Connection string is null");
@@ -235,7 +287,7 @@ public class EFCoreDatabaseManager : IDatabaseManager
// Usa la factory per ottenere il provider appropriato in base al tipo di database
var schemaProvider = DatabaseSchemaProviderFactory.CreateProvider(_options.DatabaseType);
// Usa il provider per ottenere lo schema della tabella
// Usa il provider per ottenere lo schema della tabella utilizzando la connection string corrente del DbContext
var connectionString = _context.Database.GetConnectionString();
if (connectionString == null)
throw new InvalidOperationException("Connection string is null");
@@ -257,14 +309,45 @@ public class EFCoreDatabaseManager : IDatabaseManager
{
var records = new List<Dictionary<string, object>>();
// Usa la stessa connection string utilizzata per il discovery dello schema
var connectionString = _context.Database.GetConnectionString();
if (connectionString == null)
throw new InvalidOperationException("Connection string is null");
// Usa la connessione del DbContext che è stata aggiornata se è stato chiamato ChangeDatabaseAsync
if (_context.Database.GetDbConnection().State != ConnectionState.Open)
{
await _context.Database.OpenConnectionAsync();
}
// Determina il tipo di connessione in base al DatabaseType
using var connection = CreateConnection(connectionString);
await connection.OpenAsync();
var connection = _context.Database.GetDbConnection();
// Debug: verifica il database attuale della connessione
Console.WriteLine($"GetAllRecordsAsync: Database connessione prima: {connection.Database}");
// Estrai il database target dalla connection string del DbContext
var connectionString = _context.Database.GetConnectionString();
Console.WriteLine($"GetAllRecordsAsync: Connection string completa: {connectionString}");
if (!string.IsNullOrEmpty(connectionString))
{
var builder = new SqlConnectionStringBuilder(connectionString);
var targetDatabase = builder.InitialCatalog;
Console.WriteLine($"GetAllRecordsAsync: InitialCatalog dalla connection string: '{targetDatabase}'");
Console.WriteLine($"GetAllRecordsAsync: Database corrente connessione: '{connection.Database}'");
// Se il database della connessione non corrisponde al target, forza il cambio
if (!string.IsNullOrEmpty(targetDatabase) && !string.Equals(connection.Database, targetDatabase, StringComparison.OrdinalIgnoreCase))
{
Console.WriteLine($"GetAllRecordsAsync: MISMATCH RILEVATO! Forzando cambio database da '{connection.Database}' a '{targetDatabase}'");
await connection.ChangeDatabaseAsync(targetDatabase);
Console.WriteLine($"GetAllRecordsAsync: Database connessione dopo cambio: '{connection.Database}'");
}
else
{
Console.WriteLine($"GetAllRecordsAsync: Database già corretto: '{connection.Database}' = '{targetDatabase}'");
}
}
else
{
Console.WriteLine("GetAllRecordsAsync: ATTENZIONE! Connection string è vuota!");
}
using var command = connection.CreateCommand();
@@ -319,9 +402,13 @@ public class EFCoreDatabaseManager : IDatabaseManager
if (currentConnectionString == null)
throw new InvalidOperationException("Connection string is null");
Console.WriteLine($"ChangeDatabaseAsync: Connessione corrente: {currentConnectionString}");
// Crea una nuova connection string con il database specificato
var newConnectionString = UpdateConnectionStringDatabase(currentConnectionString, databaseName);
Console.WriteLine($"ChangeDatabaseAsync: Nuova connessione: {newConnectionString}");
// Ricrea il contesto con la nuova connection string
var optionsBuilder = new DbContextOptionsBuilder<ExistingDatabaseContext>();
@@ -350,6 +437,8 @@ public class EFCoreDatabaseManager : IDatabaseManager
// Testa la connessione al nuovo database
await _context.Database.OpenConnectionAsync();
Console.WriteLine($"ChangeDatabaseAsync: Connessione aggiornata verificata: {_context.Database.GetConnectionString()}");
}
catch (Exception ex)
{
@@ -406,12 +495,45 @@ public class EFCoreDatabaseManager : IDatabaseManager
{
try
{
var connectionString = _context.Database.GetConnectionString();
if (connectionString == null)
throw new InvalidOperationException("Connection string is null");
// Usa la connessione del DbContext che è stata aggiornata se è stato chiamato ChangeDatabaseAsync
if (_context.Database.GetDbConnection().State != ConnectionState.Open)
{
await _context.Database.OpenConnectionAsync();
}
using var connection = CreateConnection(connectionString);
await connection.OpenAsync();
var connection = _context.Database.GetDbConnection();
// Debug: verifica il database attuale della connessione
Console.WriteLine($"GetPrimaryKeyFieldAsync: Database connessione prima: {connection.Database}");
// Estrai il database target dalla connection string del DbContext
var connectionString = _context.Database.GetConnectionString();
Console.WriteLine($"GetPrimaryKeyFieldAsync: Connection string completa: {connectionString}");
if (!string.IsNullOrEmpty(connectionString))
{
var builder = new SqlConnectionStringBuilder(connectionString);
var targetDatabase = builder.InitialCatalog;
Console.WriteLine($"GetPrimaryKeyFieldAsync: InitialCatalog dalla connection string: '{targetDatabase}'");
Console.WriteLine($"GetPrimaryKeyFieldAsync: Database corrente connessione: '{connection.Database}'");
// Se il database della connessione non corrisponde al target, forza il cambio
if (!string.IsNullOrEmpty(targetDatabase) && !string.Equals(connection.Database, targetDatabase, StringComparison.OrdinalIgnoreCase))
{
Console.WriteLine($"GetPrimaryKeyFieldAsync: MISMATCH RILEVATO! Forzando cambio database da '{connection.Database}' a '{targetDatabase}'");
await connection.ChangeDatabaseAsync(targetDatabase);
Console.WriteLine($"GetPrimaryKeyFieldAsync: Database connessione dopo cambio: '{connection.Database}'");
}
else
{
Console.WriteLine($"GetPrimaryKeyFieldAsync: Database già corretto: '{connection.Database}' = '{targetDatabase}'");
}
}
else
{
Console.WriteLine("GetPrimaryKeyFieldAsync: ATTENZIONE! Connection string è vuota!");
}
using var command = connection.CreateCommand();