RawQueryKit 1.0.0
dotnet add package RawQueryKit --version 1.0.0
NuGet\Install-Package RawQueryKit -Version 1.0.0
<PackageReference Include="RawQueryKit" Version="1.0.0" />
<PackageVersion Include="RawQueryKit" Version="1.0.0" />
<PackageReference Include="RawQueryKit" />
paket add RawQueryKit --version 1.0.0
#r "nuget: RawQueryKit, 1.0.0"
#:package RawQueryKit@1.0.0
#addin nuget:?package=RawQueryKit&version=1.0.0
#tool nuget:?package=RawQueryKit&version=1.0.0
RawQueryKit
RawQueryKit is a lightweight, high-performance, and secure C# library that brings Sieve-style filtering, sorting, and pagination to raw SQL, ADO.NET, and Dapper queries — without requiring Entity Framework.
It parses URL query strings, validates field-level access permissions, prevents SQL injection via parameterized queries, and outputs SQL clauses tailored to your specific database dialect.

Table of Contents
- Features
- Supported Databases
- Installation
- Quick Start
- Querying the API
- Filter Operators Reference
- API Reference
- Error Handling
- Code Contributors
- License
Features
- Attribute & Fluent Configuration — Mark properties as filterable/sortable using
[RawQueryField]attributes, or configure them programmatically with a fluent builder API. - Zero DI Setup Required — Use the high-performance
RawQuery.For<T>()static factory anywhere in your codebase; processors are cached automatically. - SQL Injection Prevention — All filter values are emitted as named parameters (
@rq_f_...) and never interpolated into the SQL string. - AND/OR Precedence Parsing — Comma-separated terms within a group are ANDed; pipe-separated groups are ORed. Grouping is emitted as bracketed SQL.
- Rich Filter Operator Set — 24 operators covering equality, comparison,
IN/NOT IN,LIKE-based pattern matching, case-insensitive variants, andIS NULL/IS NOT NULL. - Smart Comma Parsing — Intelligently distinguishes between commas as AND-separators and commas that are part of a string value (e.g.
name==Hello, World). - Dialect-Aware Pagination — Emits
OFFSET … FETCH NEXTfor SQL Server andLIMIT … OFFSETfor PostgreSQL, MySQL, and SQLite. - Default Sort Fallbacks — Configure fallback sort columns for when the client provides no
sortsparameter. - Custom SQL Expressions — Map a field to any SQL expression, not just a column name (e.g.
COALESCE(p.Description, '')).
Supported Databases
RawQueryKit is database-agnostic at the query-building level. Pass the appropriate SqlDialect and it handles the rest:
| Dialect | Enum Value | Pagination Style |
|---|---|---|
| Microsoft SQL Server | SqlDialect.SqlServer |
OFFSET x ROWS FETCH NEXT y ROWS ONLY |
| PostgreSQL | SqlDialect.PostgreSql |
LIMIT y OFFSET x |
| MySQL | SqlDialect.MySql |
LIMIT y OFFSET x |
| SQLite | SqlDialect.Sqlite |
LIMIT y OFFSET x |
Note: SQL Server requires an
ORDER BYclause when paginating. If no sort is provided (and no default sort is configured), RawQueryKit automatically injectsORDER BY (SELECT NULL)to satisfy this constraint.
Installation
Core Package
dotnet add package RawQueryKit
Dapper Integration (optional)
dotnet add package RawQueryKit.Dapper
Quick Start
1. Decorate Your Model
Apply [RawQueryField] to any properties you want to expose for filtering or sorting. The Name maps to the URL parameter name, and Column maps to the actual SQL column or expression.
using RawQueryKit;
public class Product
{
[RawQueryField(Name = "id", Column = "p.Id", CanFilter = true, CanSort = true)]
public Guid Id { get; set; }
[RawQueryField(Name = "name", Column = "p.Name", CanFilter = true, CanSort = true)]
public string Name { get; set; } = null!;
[RawQueryField(Name = "price", Column = "p.Price", CanFilter = true, CanSort = true)]
public decimal Price { get; set; }
[RawQueryField(Name = "category_id", Column = "p.CategoryId", CanFilter = true, CanSort = false)]
public int? CategoryId { get; set; }
// IsDefaultSort = true: used when no ?sorts= is provided by the client
[RawQueryField(Name = "created_at", Column = "p.CreatedAt", CanFilter = false, CanSort = true,
IsDefaultSort = true, DefaultSortDescending = true)]
public DateTime CreatedAt { get; set; }
}
2. Use in a Controller or Service
RawQuery.For<T>() returns a cached processor — no DI registration required.
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Data.Sqlite;
using RawQueryKit;
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
private readonly AppDbContext _db;
public ProductsController(AppDbContext db) => _db = db;
[HttpGet]
public async Task<IActionResult> GetProducts([FromQuery] RawQueryOptions options)
{
// 1. Build SQL clauses and parameters
var result = RawQuery.For<Product>(SqlDialect.Sqlite).Build(options);
// 2. Map parameters to the driver-specific parameter type
var sqlParams = result.Parameters
.Select(p => new SqliteParameter(p.Key, p.Value ?? DBNull.Value))
.ToArray();
// 3. Assemble the final SQL
string sql = $@"
SELECT *
FROM Products p
{result.GetWhereClause(includeWhereKeyword: true)}
{result.GetOrderByClause(includeOrderByKeyword: true)}
{result.GetPaginationClause()}
";
// 4. Execute
var products = await _db.Products.FromSqlRaw(sql, sqlParams).ToListAsync();
return Ok(products);
}
}
3. Usage with Dapper (RawQueryKit.Dapper)
The companion package provides a single extension method that handles the entire pipeline — building clauses, mapping parameters to DynamicParameters, and appending the SQL — in one call.
using RawQueryKit.Dapper;
// In your service or repository:
var products = await dbConnection.QueryWithRawQueryKitAsync<Product>(
baseSql: "SELECT * FROM Products p",
options: options,
dialect: SqlDialect.PostgreSql
);
The generated SQL is equivalent to:
SELECT * FROM Products p
WHERE (p.Price > @rq_f_Price_0)
ORDER BY p.CreatedAt DESC
LIMIT @rq_limit OFFSET @rq_offset
4. Fluent Configuration (No Attributes)
If you can't or don't want to annotate your model, configure fields via the fluent builder passed to the RawQueryProcessor<T> constructor:
var processor = new RawQueryProcessor<Product>(SqlDialect.SqlServer, cfg =>
{
cfg.Property(p => p.Id)
.HasName("id")
.HasColumn("p.Id")
.CanFilter()
.CanSort();
cfg.Property(p => p.Name)
.HasName("name")
.HasColumn("p.Name")
.CanFilter()
.CanSort();
cfg.Property(p => p.Price)
.HasName("price")
.HasColumn("p.Price")
.CanFilter()
.CanSort(false); // Sortable is disabled
});
var result = processor.Build(options);
Tip: Fluent configuration and
[RawQueryField]attributes can be combined. Attributes are scanned first, then the fluent builder can override or add fields.
Querying the API
Clients use standard URL query parameters. RawQueryKit reads them from the RawQueryOptions object (which ASP.NET Core binds automatically via [FromQuery]).
| Goal | Example URL |
|---|---|
| Filter by a single field | GET /api/products?filters=price>500 |
| Filter with equality | GET /api/products?filters=name==Laptop |
| Case-insensitive match | GET /api/products?filters=name==*laptop |
| Contains substring | GET /api/products?filters=name@=*book |
| Multiple filters (AND) | GET /api/products?filters=price>500,name@=*laptop |
| OR grouping | GET /api/products?filters=price>500\|name@=*laptop |
| IN array | GET /api/products?filters=category_id^=1,2,3 |
| Null check | GET /api/products?filters=category_id==null |
| Sort ascending | GET /api/products?sorts=name |
| Sort descending | GET /api/products?sorts=-price |
| Multi-column sort | GET /api/products?sorts=-price,name |
| Pagination | GET /api/products?page=2&pageSize=20 |
| Combined | GET /api/products?filters=price>100&sorts=-created_at&page=1&pageSize=25 |
AND/OR Grouping Logic
Filters follow standard precedence rules: AND binds tighter than OR.
a,b|c,dis parsed as(a AND b) OR (c AND d)- Each group becomes a bracketed SQL expression joined by
OR
Filter Operators Reference
| Operator | SQL Generated | Description |
|---|---|---|
== |
col = @p |
Equals |
!= |
col != @p |
Not Equals |
==null |
col IS NULL |
Is Null |
!=null |
col IS NOT NULL |
Is Not Null |
^= |
col IN (@p0, @p1, ...) |
In Array |
!^= |
col NOT IN (@p0, @p1, ...) |
Not In Array |
> |
col > @p |
Greater Than |
< |
col < @p |
Less Than |
>= |
col >= @p |
Greater Than or Equal |
<= |
col <= @p |
Less Than or Equal |
==* |
LOWER(col) = LOWER(@p) |
Case-insensitive Equals |
!=* |
LOWER(col) != LOWER(@p) |
Case-insensitive Not Equals |
@= |
col LIKE '%val%' |
Case-sensitive Contains |
!@= |
col NOT LIKE '%val%' |
Case-sensitive Not Contains |
@=* |
LOWER(col) LIKE LOWER('%val%') |
Case-insensitive Contains |
!@=* |
LOWER(col) NOT LIKE LOWER('%val%') |
Case-insensitive Not Contains |
_= |
col LIKE 'val%' |
Case-sensitive Starts With |
!_= |
col NOT LIKE 'val%' |
Case-sensitive Not Starts With |
_=* |
LOWER(col) LIKE LOWER('val%') |
Case-insensitive Starts With |
!_=* |
LOWER(col) NOT LIKE LOWER('val%') |
Case-insensitive Not Starts With |
*-= |
col LIKE '%val' |
Case-sensitive Ends With |
!*-= |
col NOT LIKE '%val' |
Case-sensitive Not Ends With |
*-=* |
LOWER(col) LIKE LOWER('%val') |
Case-insensitive Ends With |
!*-=* |
LOWER(col) NOT LIKE LOWER('%val') |
Case-insensitive Not Ends With |
Note: String-only operators (
@=,_=,*-=,==*,!=*, and their variants) throw anArgumentExceptionif applied to a non-string property.
API Reference
RawQueryFieldAttribute
Applied to model properties to declare filtering and sorting permissions.
| Property | Type | Default | Description |
|---|---|---|---|
Name |
string |
Property name (lowercase) | The query string key used by clients |
Column |
string |
Property name | The SQL column name or expression |
CanFilter |
bool |
true |
Allow this field in ?filters= |
CanSort |
bool |
true |
Allow this field in ?sorts= |
IsDefaultSort |
bool |
false |
Use as fallback sort when no ?sorts= is provided |
DefaultSortDescending |
bool |
false |
Direction of the default sort |
RawQueryOptions
Bind this from [FromQuery] in your controller, or construct it manually in services.
| Property | Type | Default | Description |
|---|---|---|---|
Filters |
string |
null |
Filter expression (e.g. "price>100,name@=*book") |
Sorts |
string |
null |
Sort expression (e.g. "-price,name") |
Page |
int? |
null |
1-indexed page number |
PageSize |
int? |
null |
Number of records per page |
DefaultPageSize |
int |
10 |
Fallback page size when PageSize is not provided |
MaxPageSize |
int |
100 |
Upper cap on PageSize |
RawQueryResult
Returned by RawQueryProcessor<T>.Build(options).
| Member | Description |
|---|---|
WhereClause |
Raw SQL condition string (without the WHERE keyword) |
OrderByClause |
Raw SQL sort string (without ORDER BY) |
PaginationClause |
Raw SQL pagination string (dialect-specific) |
Parameters |
Dictionary<string, object> of all named parameters to pass to ADO.NET / Dapper |
GetWhereClause(bool) |
Returns WhereClause, optionally prefixed with WHERE |
GetOrderByClause(bool) |
Returns OrderByClause, optionally prefixed with ORDER BY |
GetPaginationClause() |
Returns PaginationClause, or string.Empty if pagination was not requested |
RawQuery Static Factory
// Returns a cached RawQueryProcessor<T> for the given dialect.
// One processor is cached per type; if your app uses multiple dialects
// for the same model, use DI with RawQueryProcessor<T> directly.
RawQueryProcessor<T> processor = RawQuery.For<T>(SqlDialect dialect = SqlDialect.SqlServer);
Error Handling
| Situation | Exception Thrown |
|---|---|
Client attempts to filter on a field where CanFilter = false |
UnauthorizedAccessException |
Client attempts to sort on a field where CanSort = false |
UnauthorizedAccessException |
A string-only operator (e.g. @=) is used on a non-string property |
ArgumentException |
| A filter value cannot be converted to the property's type | ArgumentException |
| An unrecognized filter operator is used | NotSupportedException |
Code Contributors

License
This project is licensed under the MIT License.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0 | 43 | 6/19/2026 |