Skip to content

API Reference

AmirHosseinMp02 edited this page Aug 1, 2026 · 1 revision

API Reference

Every public type and member, by package. Behaviour is described on the linked pages; this is the signature list.

All packages target .NET 10 and are version 2.0.0.


PepperX.QueryForge

No dependencies. Namespace PepperX.QueryForge unless stated.

Query model

public class Query
{
    public QueryCriteria Criteria { get; set; }
    public QueryPaging Paging { get; set; }
    public IReadOnlyList<string> SelectColumns { get; set; }
    public IReadOnlyList<SortDescriptor> SortColumns { get; set; }
    public IReadOnlyList<GroupByDescriptor> GroupByColumns { get; set; }
}

public record QueryCriteria
{
    public IReadOnlyList<ConditionGroup> Groups { get; init; }
    public Logic Logic { get; init; }
    public QueryCriteria(IReadOnlyList<ConditionGroup>? groups = null, Logic logic = Logic.And);
}

public record ConditionGroup
{
    public IReadOnlyList<Condition> Conditions { get; init; }
    public Logic Logic { get; init; }
    public ConditionGroup(IReadOnlyList<Condition>? conditions = null, Logic logic = Logic.And);
}

public record Condition(
    string ColumnName,
    ConditionOperator Operator,
    object? Value = null,
    object? ValueTo = null);

public record QueryPaging(int Size = 12, int Number = 1);

public record SortDescriptor(string ColumnName, SortOrder SortOrder = SortOrder.Ascending)
    : IColumnDescriptor;

public record GroupByDescriptor(string ColumnName, SortOrder SortOrder = SortOrder.Ascending)
    : IColumnDescriptor;

public interface IColumnDescriptor
{
    string ColumnName { get; }
}

See Query Model.

Enumerations

public enum Logic { And, Or, AndNot, OrNot }                      // 0, 1, 2, 3
public enum SortOrder { Ascending, Descending }                   // 0, 1
public enum QueryResultType { Flat, Grouped }                     // 0, 1
public enum QueryValidationMode { SilentStrip, ThrowException }   // 0, 1

public enum ConditionOperator
{
    Equals, NotEquals, Contains, NotContains,                     // 0, 1, 2, 3
    StartsWith, EndsWith, LessThan, GreaterThan,                  // 4, 5, 6, 7
    LessThanOrEqualTo, GreaterThanOrEqualTo, Between              // 8, 9, 10
}

Results

public record QueryResult<TModel>
{
    public QueryResultMeta Meta { get; init; }
    public IReadOnlyList<TModel> Models { get; init; }
    public IReadOnlyList<HierarchyNode<TModel>> Groups { get; init; }
}

public record QueryResultMeta(QueryResultMetaTotal Total, QueryResultType Type);
public record QueryResultMetaTotal(int Rows, int Pages);

public record HierarchyNode<TModel>(
    object? Key,
    int Count,
    IReadOnlyList<HierarchyNode<TModel>>? SubGroups,
    IReadOnlyList<TModel>? Items);

See Results and Metadata.

Builders

public static class QueryBuilder
{
    public static QueryFluent New();
    public static QueryFluent Select(params string[] columns);
    public static QueryFluent Where(QueryCriteria criteria);
    public static QueryFluent Sort(params SortDescriptor[] sorts);
    public static QueryFluent GroupBy(params GroupByDescriptor[] groups);
    public static QueryFluent Page(int size, int number = 1);
}

public class QueryFluent
{
    public QueryFluent();
    public QueryFluent(Query query);

    public QueryFluent Select(params string[] columns);
    public QueryFluent Where(QueryCriteria criteria);
    public QueryFluent Sort(params SortDescriptor[] sorts);
    public QueryFluent GroupBy(params GroupByDescriptor[] groups);
    public QueryFluent Page(int size, int number = 1);
    public Query Build();
}

See Fluent Builders.

Validation

public static class QueryValidationExtensions
{
    public static Query Validate(this Query query,
        Action<QueryValidationRules> configure,
        QueryValidationMode mode = QueryValidationMode.SilentStrip);

    public static Query Validate(this Query query,
        QueryValidationRules rules,
        QueryValidationMode mode = QueryValidationMode.SilentStrip);

    // Extension points used by provider-specific validators.
    public static void ApplyBaseRules(Query query, QueryValidationRules rules,
        List<string> errors, QueryValidationMode mode);
    public static void ThrowIfErrors(List<string> errors, QueryValidationMode mode);
}

public class QueryValidationRules
{
    public QueryValidationRules PageSize(Action<QueryPagingRuleBuilder> configure);
    public QueryValidationRules Select(Action<QueryColumnRuleBuilder> configure);
    public QueryValidationRules Sort(Action<QueryColumnRuleBuilder> configure);
    public QueryValidationRules GroupBy(Action<QueryColumnRuleBuilder> configure);
    public QueryValidationRules Where(Action<QueryColumnRuleBuilder> configure);
}

public class QueryColumnRuleBuilder
{
    public QueryColumnRuleBuilder Allow(params string[] columns);
    public QueryColumnRuleBuilder Deny(params string[] columns);
}

public class QueryPagingRuleBuilder
{
    public QueryPagingRuleBuilder Max(int max);
    public QueryPagingRuleBuilder Min(int min);
}

public class QueryValidationException : Exception
{
    public IReadOnlyList<string> InvalidProperties { get; }
    public QueryValidationException(string message, IReadOnlyList<string> invalidProperties);
}

public class QueryColumnRule { }   // rule state; configured through the builder
public class QueryPagingRule { }   // rule state; configured through the builder

See Validation.

Shared semantics

Namespace PepperX.QueryForge.Querying. These are the rules every provider routes through — public so a custom provider can reuse them.

public static class ConditionSemantics
{
    public static bool IsDisjunction(Logic logic);          // Or / OrNot → true
    public static bool IsNegated(Logic logic);              // AndNot / OrNot → true
    public static bool IsExecutable(Condition condition);   // does this contribute a predicate
    public static bool IsPatternOperator(ConditionOperator op);
    public static object? Unwrap(object? value);            // JsonElement / DBNull → CLR value
}

public static class ValueCoercion
{
    public static bool TryCoerce(object? value, Type targetType, out object? result);
}

public sealed class QueryValueComparer : IComparer<object?>
{
    public static readonly QueryValueComparer Instance;
    public int Compare(object? x, object? y);
    public bool AreEqual(object? x, object? y);
}

public static class HierarchyBuilder
{
    public static IReadOnlyList<HierarchyNode<TModel>> Build<TModel>(
        IReadOnlyList<TModel> rows,
        IReadOnlyList<GroupByDescriptor> groups,
        Func<TModel, string, object?>? valueAccessor = null);
}

public static class ProjectionShaper
{
    public static TModel Shape<TModel>(TModel source,
        IReadOnlyList<string> selectColumns, IReadOnlyList<string>? alsoKeep = null);

    public static IReadOnlyList<TModel> ShapeAll<TModel>(IReadOnlyList<TModel> rows,
        IReadOnlyList<string> selectColumns, IReadOnlyList<string>? alsoKeep = null);

    public static IReadOnlyList<HierarchyNode<TModel>> ShapeHierarchy<TModel>(
        IReadOnlyList<HierarchyNode<TModel>> nodes,
        IReadOnlyList<string> selectColumns, IReadOnlyList<string>? alsoKeep = null);
}

public static class PropertyAccessor
{
    public static Func<TModel, string, object?> For<TModel>();
    public static bool Exists<TModel>(string? columnName);
}

public static class InMemoryQueryEngine
{
    public static QueryResult<TModel> Apply<TModel>(
        IEnumerable<TModel> source, Query query,
        Func<TModel, string, object?>? valueAccessor = null);

    public static bool Matches<TModel>(
        TModel row, QueryCriteria criteria,
        Func<TModel, string, object?> accessor,
        Func<string, bool>? columnExists = null);
}

See Query Semantics.


PepperX.QueryForge.Dapper

Depends on Dapper 2.1.79 and Microsoft.Extensions.DependencyInjection.Abstractions 10.0.9. No database driver.

Query model additions

Namespace PepperX.QueryForge.Dapper.

public class DapperQuery : PepperX.QueryForge.Query
{
    public DapperQueryObject? Object { get; set; }
}

public record DapperQueryObject(
    string Name,
    string Schema = "",
    DapperObjectType Type = DapperObjectType.Auto,
    IReadOnlyDictionary<string, object?>? Parameters = null);

public enum DapperObjectType { Auto = 0, Table = 1, View = 2, TVF = 3, SP = 4 }

public enum DapperDatabaseProvider { MSSQL, MySQL, PostgreSQL, Oracle, SQLite }

Services and registration

public interface IDapperQueryService
{
    Task<QueryResult<TModel>> QueryAsync<TModel>(
        IDbConnection connection, DapperQuery query,
        int? commandTimeout = null, IDbTransaction? transaction = null);

    Task<QueryResult<TModel>> QueryAsync<TModel>(
        DapperQuery query,
        int? commandTimeout = null, IDbTransaction? transaction = null);
}

public static class DapperQueryForgeConnectionExtensions
{
    public static Task<QueryResult<TModel>> QueryForgeAsync<TModel>(
        this IDbConnection connection, DapperQuery query,
        int? commandTimeout = null, IDbTransaction? transaction = null);
}

public static class DapperServiceCollectionExtensions
{
    public static IServiceCollection AddQueryForgeDapper(
        this IServiceCollection services, Action<DapperQueryForgeOptions>? configure = null);

    public static IServiceCollection AddQueryForgeDialect(
        this IServiceCollection services, ISqlDialect dialect);
}

public class DapperQueryForgeOptions
{
    public Func<IServiceProvider, IDbConnection>? ConnectionFactory { get; set; }
}

See Dapper Provider.

Builder

public static class DapperQueryBuilder
{
    public static DapperQueryFluent New();
    public static DapperQueryFluent New(DapperQuery dapperQuery);
    public static DapperQueryFluent FromBase(Query baseQuery);

    public static DapperQueryFluent ForObject(string name, string schema = "",
        DapperObjectType type = DapperObjectType.Auto,
        IReadOnlyDictionary<string, object?>? parameters = null);

    public static DapperQueryFluent Select(params string[] columns);
    public static DapperQueryFluent Where(QueryCriteria criteria);
    public static DapperQueryFluent Sort(params SortDescriptor[] sorts);
    public static DapperQueryFluent GroupBy(params GroupByDescriptor[] groups);
    public static DapperQueryFluent Page(int size, int number = 1);
}

public class DapperQueryFluent : QueryFluent
{
    public DapperQueryFluent(DapperQuery query);
    public static DapperQueryFluent FromBase(Query baseQuery);

    public DapperQueryFluent ForObject(string name, string schema = "",
        DapperObjectType type = DapperObjectType.Auto,
        IReadOnlyDictionary<string, object?>? parameters = null);

    public new DapperQueryFluent Select(params string[] columns);
    public new DapperQueryFluent Where(QueryCriteria criteria);
    public new DapperQueryFluent Sort(params SortDescriptor[] sorts);
    public new DapperQueryFluent GroupBy(params GroupByDescriptor[] groups);
    public new DapperQueryFluent Page(int size, int number = 1);
    public new DapperQuery Build();
}

Note DapperQueryBuilder is in the global namespace, so it needs no using beyond PepperX.QueryForge.Dapper for its parameter types.

Validation additions

public static class DapperQueryValidationExtensions
{
    public static DapperQuery Validate(this DapperQuery query,
        Action<DapperQueryValidationRules> configure,
        QueryValidationMode mode = QueryValidationMode.SilentStrip);
}

public class DapperQueryValidationRules : QueryValidationRules
{
    public DapperQueryValidationRules Object(Action<DapperObjectRuleBuilder> configure);
}

public class DapperObjectRuleBuilder
{
    public DapperObjectRuleBuilder AllowSchema(params string[] schemas);
    public DapperObjectRuleBuilder DenySchema(params string[] schemas);
    public DapperObjectRuleBuilder AllowTable(params string[] tables);
    public DapperObjectRuleBuilder DenyTable(params string[] tables);
    public DapperObjectRuleBuilder RequireName(bool require = true);
}

public class DapperObjectRule { }   // rule state; configured through the builder

See Validation.

Compiler

Namespace PepperX.QueryForge.Dapper.Compiler.

public sealed class SqlQueryCompiler
{
    public SqlQueryCompiler(ISqlDialect dialect);
    public ISqlDialect Dialect { get; }

    public CompiledSql CompileSchemaProbe(DapperQuery query);
    public CompiledSql CompileRows(DapperQuery query, ColumnWhitelist columns);
    public CompiledSql CompileRowCount(DapperQuery query, ColumnWhitelist columns);
    public CompiledSql CompileGroupKeys(DapperQuery query, ColumnWhitelist columns);
    public CompiledSql CompileGroupCount(DapperQuery query, ColumnWhitelist columns);
    public CompiledSql CompileGroupRows(DapperQuery query, ColumnWhitelist columns,
        IReadOnlyList<object?> groupKeys);
}

public sealed record CompiledSql(string Text, IReadOnlyDictionary<string, object?> Parameters);

public sealed class ColumnWhitelist
{
    public ColumnWhitelist(IEnumerable<string> columns);
    public ColumnWhitelist(IEnumerable<KeyValuePair<string, Type?>> columns);

    public IReadOnlyCollection<string> Columns { get; }
    public bool Contains(string? columnName);
    public Type? TypeOf(string? columnName);
}

public interface ISqlDialect
{
    DapperDatabaseProvider ProviderType { get; }
    string DefaultSchema { get; }
    string QuoteIdentifier(string identifier);
    string ParameterReference(string name);
    string PagingClause(int offset, int size);
    bool RequiresOrderByForPaging { get; }
    string OrderByFallback { get; }
    string NullOrdering(SortOrder order);
    string EscapeLikeValue(string value);
    string LikeEscapeClause { get; }
    bool SupportsTableValuedFunctions { get; }
    bool SupportsStoredProcedures { get; }

    string BuildSource(string schema, string name, DapperObjectType type,
        IReadOnlyList<string> argumentReferences);

    string BuildStoredProcedureCall(string schema, string name,
        IReadOnlyList<KeyValuePair<string, string>> argumentNames);
}

See Dapper: Generated SQL and Dapper: Dialects.

Dialects

Namespace PepperX.QueryForge.Dapper.Dialects. All sealed, all parameterless, all stateless.

public sealed class SqlServerDialect  : ISqlDialect { }
public sealed class PostgreSqlDialect : ISqlDialect { }
public sealed class MySqlDialect      : ISqlDialect { }
public sealed class OracleDialect     : ISqlDialect { }
public sealed class SqliteDialect     : ISqlDialect { }

PepperX.QueryForge.EFCore

Depends on Microsoft.EntityFrameworkCore 10.0.0.

namespace PepperX.QueryForge.EFCore;

public static class QueryForgeQueryableExtensions
{
    public static Task<QueryResult<TModel>> ToQueryResultAsync<TModel>(
        this IQueryable<TModel> source, Query query, CancellationToken cancellationToken = default);

    public static IQueryable<TModel> ApplyQuery<TModel>(this IQueryable<TModel> source, Query query);
    public static IQueryable<TModel> ApplyFilter<TModel>(this IQueryable<TModel> source, Query query);
    public static IQueryable<TModel> ApplySort<TModel>(this IQueryable<TModel> source, Query query);
    public static IQueryable<TModel> ApplyPaging<TModel>(this IQueryable<TModel> source, Query query);
    public static IQueryable<TModel> ApplyProjection<TModel>(
        this IQueryable<TModel> source, Query query, IReadOnlyList<string>? alsoKeep = null);
}
namespace PepperX.QueryForge.EFCore.Translation;

public static class ExpressionCompiler
{
    public static Expression<Func<TModel, bool>>? BuildPredicate<TModel>(QueryCriteria criteria);
    public static LambdaExpression? BuildSelector<TModel>(string columnName);
    public static Expression<Func<TModel, TModel>>? BuildProjection<TModel>(
        IReadOnlyList<string> selectColumns, IReadOnlyList<string>? alsoKeep = null);
    public static LambdaExpression? BuildNullRank<TModel>(PropertyInfo property, bool nullsFirst);
    public static Expression<Func<TKey, int>>? BuildKeyNullRank<TKey>(bool nullsFirst);
    public static PropertyInfo? ResolveProperty<TModel>(string? columnName);
}

See EF Core Provider.


PepperX.QueryForge.InMemory

No dependencies beyond the core package.

namespace PepperX.QueryForge.InMemory;

public static class InMemoryQueryExtensions
{
    public static QueryResult<TModel> ToQueryResult<TModel>(
        this IEnumerable<TModel> source, Query query,
        Func<TModel, string, object?>? valueAccessor = null);

    public static Task<QueryResult<TModel>> ToQueryResultAsync<TModel>(
        this IEnumerable<TModel> source, Query query,
        Func<TModel, string, object?>? valueAccessor = null,
        CancellationToken cancellationToken = default);

    public static IEnumerable<TModel> ApplyQuery<TModel>(this IEnumerable<TModel> source, Query query,
        Func<TModel, string, object?>? valueAccessor = null);
    public static IEnumerable<TModel> ApplyFilter<TModel>(this IEnumerable<TModel> source, Query query,
        Func<TModel, string, object?>? valueAccessor = null);
    public static IEnumerable<TModel> ApplySort<TModel>(this IEnumerable<TModel> source, Query query,
        Func<TModel, string, object?>? valueAccessor = null);
    public static IEnumerable<TModel> ApplyPaging<TModel>(this IEnumerable<TModel> source, Query query);
    public static IEnumerable<TModel> ApplyProjection<TModel>(this IEnumerable<TModel> source, Query query);
}

public static class InMemoryAccessors
{
    public static Func<TRow, string, object?> ForDictionary<TRow>()
        where TRow : IReadOnlyDictionary<string, object?>;

    public static Func<TModel, string, object?> WithColumnMap<TModel>(
        IReadOnlyDictionary<string, string> columnToProperty);
}

See In-Memory Provider.


Internal types

These are internal and are listed only so their names are recognisable in a stack trace. They are not part of the public contract and can change without a major version.

Type Role
DapperRegistry holds one executor per engine; resolves by connection type name
QueryExecutor runs compiled statements and assembles the result
SchemaCache discovers and caches each object's column whitelist
QueryForgeParameters binds parameters and forces BindByName where the driver supports it
DapperEngine holds the registry backing the IDbConnection extension
DapperQueryService the IDapperQueryService implementation

Test projects have InternalsVisibleTo; applications do not.

Clone this wiki locally