// Probe LECTURE SEULE ciblé : chargement RendezVous du snapshot mobile (bulk). // // Reproduit EXACTEMENT le chemin du snapshot RendezVous corrigé : // MobileSnapshotService.LoadRendezVousAsync // -> IRendezVousService.SearchDetailsAsync // -> BatikRendezVousRepository.SearchDetailsAsync (bulk, pas de N+1) // // Le probe compte le nombre de requêtes HFSQL exécutées pour prouver que le // chargement est bulk (nombre borné, indépendant du nombre de rendez-vous) et // qu'aucun GetById par rendez-vous n'est émis. // // La quarantaine read-only des anomalies physiques (PL_DateFin < PL_DateDebut) // est appliquée par le repository AVANT le mapper : aucune DomainValidationException // ne doit être levée pour les 53 anomalies source. // // Aucune écriture. Aucun password affiché. Aucune donnée personnelle loggée. using Microsoft.Extensions.Configuration; using Omika.Core.Contracts.Common; using Omika.Core.Domain.Commands; using Omika.Core.Domain.Entities.Planning; using Omika.Core.Interfaces; using Omika.Data.Configuration; using Omika.Data.Connection; using Omika.Data.HfSql.Connection; using Omika.Data.HfSql.Metadata; using Omika.Data.Repositories.Batik; // --- 1. Charger la configuration Hfsql (avec FilePasswords) --- var configPath = Path.GetFullPath(Path.Combine( Directory.GetCurrentDirectory(), "config", "hfsql-test.local.json")); var config = new ConfigurationBuilder() .AddJsonFile(configPath, optional: false, reloadOnChange: false) .Build(); var hfsqlOptions = new HfsqlOptions(); config.GetSection(HfsqlOptions.SectionName).Bind(hfsqlOptions); Console.WriteLine($"=== Probe RendezVous Snapshot Bulk (base {hfsqlOptions.DatabaseName}) ==="); Console.WriteLine(); // --- 2. Reproduire le mécanisme de l'API : BusinessOleDbConnection --- var factory = new HfSqlOleDbConnectionFactory(); await using var business = new BusinessOleDbConnection(hfsqlOptions, factory); // Wrapper compteur de requêtes HFSQL (lecture seule, délègue tout). var counting = new CountingHfsqlConnection(business); var sw = System.Diagnostics.Stopwatch.StartNew(); try { await counting.OpenAsync(); Console.WriteLine("Connexion OLE DB ouverte (mécanisme API)."); // --- 3. Repository Batik + chemin bulk du snapshot RendezVous --- IRendezVousRepository repository = new BatikRendezVousRepository(counting); var criteria = new RendezVousSearchCriteria { Page = 1, PageSize = 1000 }; using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(15)); PagedResult result = await repository.SearchDetailsAsync(criteria, cts.Token); sw.Stop(); Console.WriteLine(); Console.WriteLine("--- Résultat ---"); Console.WriteLine($"rendez-vous chargés (page) = {result.Items.Count}"); Console.WriteLine($"total rendez-vous (SearchDetails) = {result.TotalCount}"); Console.WriteLine($"requêtes HFSQL exécutées = {counting.QueryCount}"); Console.WriteLine($"elapsed = {sw.ElapsedMilliseconds} ms"); // Vérification anti-N+1 : le nombre de requêtes doit être borné et très // inférieur au nombre de rendez-vous. Avec l'ancien pattern N+1, on aurait // observé ~1 requête par rendez-vous (timeout HTTP 30s). if (result.TotalCount > 0 && counting.QueryCount >= result.TotalCount) { Console.WriteLine("ATTENTION : le nombre de requêtes est >= au nombre de rendez-vous (pattern N+1 suspect)."); } else { Console.WriteLine("OK : chargement bulk (requêtes << rendez-vous), aucun GetById par rendez-vous."); } // Aucune DomainValidationException ne doit être levée : la quarantaine des // anomalies (PL_DateFin < PL_DateDebut) est appliquée AVANT le mapper. Console.WriteLine("OK : aucune DomainValidationException (quarantaine appliquée avant mapper)."); } catch (Exception ex) { sw.Stop(); Console.WriteLine($"ERREUR: {ex.Message}"); Console.WriteLine($"elapsed = {sw.ElapsedMilliseconds} ms"); } Console.WriteLine(); Console.WriteLine("Probe RendezVous Snapshot Bulk terminé (lecture seule)."); /// /// Wrapper IHfsqlConnection qui délègue à la connexion réelle et compte les /// requêtes de lecture (QueryAsync). Lecture seule : aucune écriture. /// internal sealed class CountingHfsqlConnection : IHfsqlConnection { private readonly IHfsqlConnection _inner; public int QueryCount { get; private set; } public CountingHfsqlConnection(IHfsqlConnection inner) => _inner = inner; public HfsqlOptions Options => _inner.Options; public HfsqlConnectionState State => _inner.State; public Task OpenAsync(CancellationToken ct = default) => _inner.OpenAsync(ct); public Task CloseAsync() => _inner.CloseAsync(); public Task ExecuteScalarAsync(string sql, IReadOnlyList? parameters = null, CancellationToken ct = default) => _inner.ExecuteScalarAsync(sql, parameters, ct); public Task ExecuteNonQueryAsync(string sql, IReadOnlyList? parameters = null, CancellationToken ct = default) => _inner.ExecuteNonQueryAsync(sql, parameters, ct); public async Task>> QueryAsync( string sql, IReadOnlyList? parameters = null, CancellationToken ct = default) { QueryCount++; return await _inner.QueryAsync(sql, parameters, ct); } public Task> GetColumnsAsync(string tableName, CancellationToken ct = default) => _inner.GetColumnsAsync(tableName, ct); public ValueTask DisposeAsync() => _inner.DisposeAsync(); }