Skip to content

Repository files navigation

AutoQuery.Generator

NuGetNuGet DownloadsCILicense: MIT.NET 10 Ready

📖 Documentation site · NuGet · Changelog

Compile-time query composition for IQueryable<T> via Roslyn incremental source generators.

Add [QuerySpec(typeof(Product))] to a partial query class and AutoQuery.Generator emits strongly-typed Apply(IQueryable<Product>), BindFromQuery(...), and FromQuery(...) methods at build time. No reflection. No runtime scanning. AOT-friendly.

usingAutoQuery;usingSystem.Linq;[QuerySpec(typeof(Product))]publicpartialclassProductQuery{publicstring?Name{get;set;}publicdecimal?MinPrice{get;set;}publicdecimal?MaxPrice{get;set;}[QuerySort]publicstring?SortBy{get;set;}publicboolSortDescending{get;set;}publicintPageNumber{get;set;}=1;publicintPageSize{get;set;}=20;}varfiltered=newProductQuery{Name="Laptop",MinPrice=500,SortBy="Name",PageNumber=1,PageSize=10,}.Apply(products);// Or bind directly from HTTP query-string values:varrequestQuery=newDictionary<string,string?>{["Name"]="Laptop",["MinPrice"]="500",["SortBy"]="Name",["PageNumber"]="1",["PageSize"]="10"};varbound=ProductQuery.FromQuery(requestQuery);varfilteredFromQuery=bound.Apply(products);

Installation

dotnet add package AutoQuery.Generator

Targets netstandard2.0 and works with modern SDK-style .NET projects.


Quick start

1. Define an entity and query spec

usingAutoQuery;publicsealedclassProduct{publicstring?Name{get;set;}publicdecimalPrice{get;set;}publicboolIsActive{get;set;}}[QuerySpec(typeof(Product))]publicpartialclassProductQuery{publicstring?Name{get;set;}publicdecimal?MinPrice{get;set;}publicbool?IsActive{get;set;}}

2. Use the generated Apply method

IQueryable<Product>query=dbContext.Products;varspec=newProductQuery{Name="Phone",MinPrice=100,IsActive=true};varresult=spec.Apply(query);

3. Bind directly from query-string values

usingAutoQuery;[QuerySpec(typeof(Product))]publicpartialclassProductQuery{publicstring?Name{get;set;}publicdecimal?MinPrice{get;set;}[QuerySort]publicstring?SortBy{get;set;}publicboolSortDescending{get;set;}publicintPageNumber{get;set;}=1;publicintPageSize{get;set;}=20;}// Works with Dictionary<string, string?>varquery=ProductQuery.FromQuery(newDictionary<string,string?>{["Name"]="Laptop",["MinPrice"]="500",["SortBy"]="Name",["PageNumber"]="1",["PageSize"]="10"});// Also works with ASP.NET Core Request.Query without taking an ASP.NET Core package dependency.app.MapGet("/products",(AppDbContextdb,HttpRequestrequest)=>{varspec=ProductQuery.FromQuery(request.Query);returnspec.Apply(db.Products);});

Unknown keys are ignored, malformed values are skipped, and successful conversions use invariant culture for numeric/date parsing plus case-insensitive enum parsing.

Generated output resembles:

publicpartialclassProductQuery{publicIQueryable<global::YourApp.Product>Apply(IQueryable<global::YourApp.Product>query){if(Nameis not null)query=query.Where(x =>x.Name!=null&&x.Name.Contains(Name));if(MinPriceis not null)query=query.Where(x =>x.Price>=MinPrice.Value);if(IsActiveis not null)query=query.Where(x =>x.IsActive==IsActive.Value);returnquery;}}

Attributes

[QuerySpec(typeof(TEntity))]

Marks a partial class as a query spec for the target entity.

[QuerySpec(typeof(Order))]publicpartialclassOrderQuery{}

[QueryFilter("x => x.Category.Name == value")]

Overrides the default convention and uses your custom LINQ predicate expression. The token value is replaced with the query property access.

[QueryFilter("x => x.Category.Name == value")]publicstring?CategoryName{get;set;}

[QueryIgnore]

Skips a property entirely.

[QueryIgnore]publicstring?DebugOnly{get;set;}

[QuerySort]

Marks a string? property as the requested sort field.

[QuerySort]publicstring?SortBy{get;set;}publicboolSortDescending{get;set;}

[QueryPage]

Marks pagination properties when you are not using the conventional names PageNumber and PageSize.

[QueryPage]publicintResultsPageNumber{get;set;}=1;[QueryPage]publicintResultsPageSize{get;set;}=25;

Convention-based filters

Nullable properties become filters automatically unless excluded.

Spec propertyGenerated predicate
string? Namex => x.Name != null && x.Name.Contains(Name)
bool? IsActivex => x.IsActive == IsActive.Value
int? CategoryIdx => x.CategoryId == CategoryId.Value
decimal? MinPricex => x.Price >= MinPrice.Value
decimal? MaxPricex => x.Price <= MaxPrice.Value
DateTime? CreatedFromx => x.Created >= CreatedFrom.Value
DateTime? CreatedTox => x.Created <= CreatedTo.Value

Prefix/suffix conventions:

  • Min...>=
  • Max...<=
  • ...From>=
  • ...To<=

Sorting

When the spec contains a [QuerySort] string property and a bool property named SortDescending or IsDescending, AutoQuery emits switch-based sorting.

[QuerySpec(typeof(Product))]publicpartialclassProductQuery{publicstring?Name{get;set;}publicdecimal?Price{get;set;}[QuerySort]publicstring?SortBy{get;set;}publicboolSortDescending{get;set;}}

Generated shape:

if(SortByis not null){query=(SortBy,SortDescending)switch{("Name",false)=>query.OrderBy(x =>x.Name),("Name",true)=>query.OrderByDescending(x =>x.Name),("Price",false)=>query.OrderBy(x =>x.Price),("Price",true)=>query.OrderByDescending(x =>x.Price),
_ =>query};}

Pagination

When PageNumber and PageSize are present (or [QueryPage] annotated equivalents are detected), AutoQuery emits:

query=query.Skip((PageNumber-1)*PageSize).Take(PageSize);

Example:

[QuerySpec(typeof(Product))]publicpartialclassProductQuery{publicstring?Name{get;set;}publicintPageNumber{get;set;}=1;publicintPageSize{get;set;}=20;}

HTTP query-string binding

For every [QuerySpec] class with writable supported properties, AutoQuery emits:

publicvoidBindFromQuery(IEnumerable<KeyValuePair<string,string?>>query);publicvoidBindFromQuery<TValue>(IEnumerable<KeyValuePair<string,TValue>>query)whereTValue:IEnumerable<string>;publicstaticProductQueryFromQuery(IEnumerable<KeyValuePair<string,string?>>query);publicstaticProductQueryFromQuery<TValue>(IEnumerable<KeyValuePair<string,TValue>>query)whereTValue:IEnumerable<string>;

Supported property conversions:

  • string
  • numeric types and nullable numeric types
  • bool / bool?
  • DateTime / DateTime?
  • enums and nullable enums

The generic overload is what lets Request.Query bind cleanly: IQueryCollection enumerates as KeyValuePair<string, StringValues>, and StringValues implements IEnumerable<string>, so no AutoQuery runtime dependency on ASP.NET Core is required.


Diagnostics

IdSeverityDescription
AQ001Error[QuerySpec] entity type could not be resolved.
AQ002Error[QuerySpec] target class must be declared partial.
AQ003WarningSpec has no filterable properties.

Comparison

CapabilityAutoQuery.GeneratorManual LINQArdalis.Specification
Compile-time generated Apply method
Compile-time generated query-string binding
Reflection-freeUsually ✅
Convention filters from DTO-like class
Custom inline filter expressionsManual onlyVia handwritten spec logic
Built-in sort switch generationManual onlyManual only
Built-in pagination generationManual onlyManual only
Runtime abstraction dependencyNoneNonePackage dependency

Also by the same author

🌐 Full suite overview: swevo.github.io

PackageDescription
AutoWireCompile-time DI auto-registration — [Scoped]/[Singleton]/[Transient] generates IServiceCollection code. Zero reflection.
AutoMap.GeneratorCompile-time object mapping — [Map(typeof(Dto))] generates ToDto() extension methods. Zero reflection, AOT-safe.
AutoValidate.GeneratorCompile-time FluentValidation wiring — discovers AbstractValidator<T> subclasses and generates AddValidators().
AutoResult.GeneratorCompile-time Result<T> monad — [TryWrap] generates Try*() wrappers for sync, async and void methods.
AutoDispatch.GeneratorCompile-time CQRS dispatcher — [Handler] generates a strongly-typed IDispatcher. No IRequest<T>, no reflection.
AutoLog.GeneratorCompile-time high-performance logging — [Log(Level, Message)] on a partial method generates LoggerMessage.Define. AOT-safe.
AutoHttpClient.GeneratorCompile-time typed HTTP client — [HttpClient] on an interface generates a strongly-typed client. AOT-safe Refit alternative.

Related Packages

PackageDownloadsDescription
AutoWireDownloadsCompile-time dependency injection auto-registration for
AutoMap.GeneratorDownloadsCompile-time object mapping for
AutoArchitectureDownloadsCompile-time architecture/dependency-rule enforcement for
AutoHttpClient.GeneratorDownloadsCompile-time typed HTTP client generation for
AutoDispatch.GeneratorDownloadsCompile-time CQRS dispatcher for
AutoLog.GeneratorDownloadsCompile-time high-performance logging for
AutoValidate.GeneratorDownloadsCompile-time FluentValidation wiring for

License

MIT.

About

Compile-time query composition for IQueryable<T> via Roslyn source generators. Add [QuerySpec] to a spec class — AutoQuery generates a strongly-typed Apply() method at build time.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages