using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Dapper; using GB5Shared.DTO.Framework.Login; using GB5Shared.QueryExecutor; using static GB5Shared.QueryExecutor.QueryExecutor; namespace GB5Shared.ListQuery { // ───────────────────────────────────────────────────────────────────── // SqlListHandler // // Abstract base class for all standard list query handlers. // Owns the boilerplate that would otherwise be repeated in every handler: // • SqlDialectFactory.Get(login.DatabaseType) // • IQueryExecutor.QueryPagedAsync (HandleAsync — paged result) // • IQueryExecutor.QueryStreamAsync (StreamAsync — unbounded stream) // // Subclass provides ONE override: // • BuildSql(TCriteria criteria, ISqlDialect dialect) // → (string Sql, DynamicParameters Params) // // Paging must be embedded in the SQL by SqlClauses.Paging() inside // the QB method. IQueryExecutor.QueryPagedAsync does not accept // separate pageOffset/pageSize parameters. // // Generic constraints: // TQuery : IListQuery — query record with Criteria property // TCriteria : class — typed criteria (implements ICriteria) // TResult : unconstrained — the per-row DTO // // DI / Scrutor: // This class is abstract → excluded from the Scrutor scan // (.AddClasses(c => c.Where(t => !t.IsAbstract && !t.IsInterface))). // Concrete sealed subclasses are registered automatically. No manual // DI registration is needed. // // Usage (the only code a module developer writes): // // public sealed class FooListHandler // : SqlListHandler // { // public FooListHandler(IQueryExecutor qe) : base(qe) { } // // protected override (string Sql, DynamicParameters Params) BuildSql( // FooCriteria c, ISqlDialect d) // => FooQB.BuildFooList(c, d); // } // ───────────────────────────────────────────────────────────────────── public abstract class SqlListHandler : IListHandler where TQuery : IListQuery where TCriteria : class { private readonly IQueryExecutor _qe; protected SqlListHandler(IQueryExecutor qe) => _qe = qe; // ── The one method subclasses must override ──────────────────────── /// /// Builds the parameterised SQL for the given criteria and dialect. /// Paging (OFFSET/FETCH or LIMIT/OFFSET) must be embedded here via /// SqlClauses.Paging(); it is not injected by the executor. /// protected abstract (string Sql, DynamicParameters Params) BuildSql( TCriteria criteria, ISqlDialect dialect); // ── IListHandler ───────────────────────────────── /// public async Task> HandleAsync( TQuery query, LoginDTO login, CancellationToken ct = default) { var dialect = SqlDialectFactory.Get(login.DatabaseType); var (sql, param) = BuildSql(query.Criteria, dialect); // QueryPagedAsync has no CancellationToken overload — paging is in the SQL. return await _qe.QueryPagedAsync(login, sql, param) .ConfigureAwait(false); } /// public async IAsyncEnumerable StreamAsync( TQuery query, LoginDTO login, [EnumeratorCancellation] CancellationToken ct = default) { var dialect = SqlDialectFactory.Get(login.DatabaseType); var (sql, param) = BuildSql(query.Criteria, dialect); // QueryStreamAsync accepts CancellationToken as the 4th positional arg. await foreach (var row in _qe.QueryStreamAsync(login, sql, param, ct) .ConfigureAwait(false)) yield return row; } } }