55 lines
2.5 KiB
C#
55 lines
2.5 KiB
C#
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<DbKeepAliveService> 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<SIFinanceDbContext>();
|
|
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.");
|
|
}
|
|
}
|
|
}
|