Premier commit

This commit is contained in:
2026-09-11 14:55:41 +02:00
commit 8ed04aae89
134 changed files with 36627 additions and 0 deletions
+54
View File
@@ -0,0 +1,54 @@
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.");
}
}
}