using Microsoft.EntityFrameworkCore; using SIFinance.Shared.Entities; namespace SIFinance.Api; // Envoie une requête minimale à SQL Server à intervalle régulier pour garder une // connexion du pool active. Sans ça, une connexion inutilisée trop longtemps entre // deux sessions de travail peut être coupée silencieusement par un équipement réseau // (pare-feu/routeur) entre la machine de dev et le serveur SQL distant — la requête // suivante doit alors attendre l'expiration de la connexion morte avant d'en ouvrir // une nouvelle, d'où un premier chargement lent (plusieurs secondes) après une pause. public class DbKeepAliveService(IServiceScopeFactory scopeFactory, ILogger logger) : BackgroundService { private static readonly TimeSpan Interval = TimeSpan.FromMinutes(2); // Le Dashboard (et d'autres pages) lancent plusieurs requêtes en parallèle au // chargement (demandes/factures/avoirs) — chacune a besoin de sa propre connexion // du pool. Ne garder qu'UNE connexion chaude ne profite qu'à la première requête // servie ; les autres ouvrent quand même une connexion à froid en parallèle, avec // le même délai. On chauffe donc plusieurs connexions à la fois. private const int WarmConnections = 4; protected override async Task ExecuteAsync(CancellationToken stoppingToken) { // Premier ping immédiat au démarrage : sans ça, PeriodicTimer attend un // intervalle complet avant son premier tick, donc les toutes premières // requêtes réelles de l'appli (juste après un dotnet run) n'en profitent // jamais et paient quand même le coût d'ouverture de connexion à froid. await PingMany(stoppingToken); using var timer = new PeriodicTimer(Interval); while (await timer.WaitForNextTickAsync(stoppingToken)) { await PingMany(stoppingToken); } } private Task PingMany(CancellationToken ct) => Task.WhenAll(Enumerable.Range(0, WarmConnections).Select(_ => Ping(ct))); private async Task Ping(CancellationToken ct) { try { using var scope = scopeFactory.CreateScope(); var db = scope.ServiceProvider.GetRequiredService(); await db.Database.ExecuteSqlRawAsync("SELECT 1", ct); } catch (Exception ex) when (ex is not OperationCanceledException) { logger.LogWarning(ex, "Échec du ping de maintien de connexion vers SQL Server."); } } }