Files
2026-10-23 18:46:50 +02:00

133 lines
5.0 KiB
C#

// Probe LECTURE SEULE ciblé : chargement Clients du snapshot mobile.
//
// Reproduit EXACTEMENT le chemin du snapshot Clients corrigé :
// MobileSnapshotService.LoadClientsAsync
// -> ClientService.SearchDetailsAsync
// -> BatikClientRepository.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 clients) et
// qu'aucun GetById par client n'est émis.
//
// Aucune écriture. Aucun password affiché. Aucune donnée personnelle loggée.
using Microsoft.Extensions.Configuration;
using Omika.Core.Contracts.Clients;
using Omika.Core.Contracts.Common;
using Omika.Core.Domain.Entities.Clients;
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 Client Snapshot (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 Clients ---
IClientRepository repository = new BatikClientRepository(counting);
var criteria = new ClientSearchCriteria
{
Page = 1,
PageSize = 1000
};
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(15));
PagedResult<Client> result = await repository.SearchDetailsAsync(criteria, cts.Token);
sw.Stop();
Console.WriteLine();
Console.WriteLine("--- Résultat ---");
Console.WriteLine($"clients chargés (page) = {result.Items.Count}");
Console.WriteLine($"total clients (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 clients. Avec l'ancien pattern N+1, on aurait
// observé ~1 requête par client (18824 requêtes).
if (result.TotalCount > 0 && counting.QueryCount >= result.TotalCount)
{
Console.WriteLine("ATTENTION : le nombre de requêtes est >= au nombre de clients (pattern N+1 suspect).");
}
else
{
Console.WriteLine("OK : chargement bulk (requêtes << clients), aucun GetById par client.");
}
}
catch (Exception ex)
{
sw.Stop();
Console.WriteLine($"ERREUR: {ex.Message}");
Console.WriteLine($"elapsed = {sw.ElapsedMilliseconds} ms");
}
Console.WriteLine();
Console.WriteLine("Probe Client Snapshot terminé (lecture seule).");
/// <summary>
/// Wrapper IHfsqlConnection qui délègue à la connexion réelle et compte les
/// requêtes de lecture (QueryAsync). Lecture seule : aucune écriture.
/// </summary>
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<object?> ExecuteScalarAsync(string sql, IReadOnlyList<object?>? parameters = null, CancellationToken ct = default)
=> _inner.ExecuteScalarAsync(sql, parameters, ct);
public Task<int> ExecuteNonQueryAsync(string sql, IReadOnlyList<object?>? parameters = null, CancellationToken ct = default)
=> _inner.ExecuteNonQueryAsync(sql, parameters, ct);
public async Task<IReadOnlyList<IReadOnlyDictionary<string, object?>>> QueryAsync(
string sql, IReadOnlyList<object?>? parameters = null, CancellationToken ct = default)
{
QueryCount++;
return await _inner.QueryAsync(sql, parameters, ct);
}
public Task<IReadOnlyList<HfsqlColumnMetadata>> GetColumnsAsync(string tableName, CancellationToken ct = default)
=> _inner.GetColumnsAsync(tableName, ct);
public ValueTask DisposeAsync() => _inner.DisposeAsync();
}