AutoQuery.Generator 1.1.0

dotnet add package AutoQuery.Generator --version 1.1.0
                    
NuGet\Install-Package AutoQuery.Generator -Version 1.1.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="AutoQuery.Generator" Version="1.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="AutoQuery.Generator" Version="1.1.0" />
                    
Directory.Packages.props
<PackageReference Include="AutoQuery.Generator" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add AutoQuery.Generator --version 1.1.0
                    
#r "nuget: AutoQuery.Generator, 1.1.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package AutoQuery.Generator@1.1.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=AutoQuery.Generator&version=1.1.0
                    
Install as a Cake Addin
#tool nuget:?package=AutoQuery.Generator&version=1.1.0
                    
Install as a Cake Tool

AutoQuery.Generator

NuGet NuGet Downloads CI License: MIT

๐Ÿ“– 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.

using AutoQuery;
using System.Linq;

[QuerySpec(typeof(Product))]
public partial class ProductQuery
{
    public string? Name { get; set; }
    public decimal? MinPrice { get; set; }
    public decimal? MaxPrice { get; set; }
    [QuerySort] public string? SortBy { get; set; }
    public bool SortDescending { get; set; }
    public int PageNumber { get; set; } = 1;
    public int PageSize { get; set; } = 20;
}

var filtered = new ProductQuery
{
    Name = "Laptop",
    MinPrice = 500,
    SortBy = "Name",
    PageNumber = 1,
    PageSize = 10,
}.Apply(products);

// Or bind directly from HTTP query-string values:
var requestQuery = new Dictionary<string, string?>
{
    ["Name"] = "Laptop",
    ["MinPrice"] = "500",
    ["SortBy"] = "Name",
    ["PageNumber"] = "1",
    ["PageSize"] = "10"
};

var bound = ProductQuery.FromQuery(requestQuery);
var filteredFromQuery = 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

using AutoQuery;

public sealed class Product
{
    public string? Name { get; set; }
    public decimal Price { get; set; }
    public bool IsActive { get; set; }
}

[QuerySpec(typeof(Product))]
public partial class ProductQuery
{
    public string? Name { get; set; }
    public decimal? MinPrice { get; set; }
    public bool? IsActive { get; set; }
}

2. Use the generated Apply method

IQueryable<Product> query = dbContext.Products;
var spec = new ProductQuery { Name = "Phone", MinPrice = 100, IsActive = true };
var result = spec.Apply(query);

3. Bind directly from query-string values

using AutoQuery;

[QuerySpec(typeof(Product))]
public partial class ProductQuery
{
    public string? Name { get; set; }
    public decimal? MinPrice { get; set; }
    [QuerySort] public string? SortBy { get; set; }
    public bool SortDescending { get; set; }
    public int PageNumber { get; set; } = 1;
    public int PageSize { get; set; } = 20;
}

// Works with Dictionary<string, string?>
var query = ProductQuery.FromQuery(new Dictionary<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", (AppDbContext db, HttpRequest request) =>
{
    var spec = ProductQuery.FromQuery(request.Query);
    return spec.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:

public partial class ProductQuery
{
    public IQueryable<global::YourApp.Product> Apply(IQueryable<global::YourApp.Product> query)
    {
        if (Name is not null)
            query = query.Where(x => x.Name != null && x.Name.Contains(Name));
        if (MinPrice is not null)
            query = query.Where(x => x.Price >= MinPrice.Value);
        if (IsActive is not null)
            query = query.Where(x => x.IsActive == IsActive.Value);
        return query;
    }
}

Attributes

[QuerySpec(typeof(TEntity))]

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

[QuerySpec(typeof(Order))]
public partial class OrderQuery { }

[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")]
public string? CategoryName { get; set; }

[QueryIgnore]

Skips a property entirely.

[QueryIgnore]
public string? DebugOnly { get; set; }

[QuerySort]

Marks a string? property as the requested sort field.

[QuerySort]
public string? SortBy { get; set; }
public bool SortDescending { get; set; }

[QueryPage]

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

[QueryPage] public int ResultsPageNumber { get; set; } = 1;
[QueryPage] public int ResultsPageSize { get; set; } = 25;

Convention-based filters

Nullable properties become filters automatically unless excluded.

Spec property Generated predicate
string? Name x => x.Name != null && x.Name.Contains(Name)
bool? IsActive x => x.IsActive == IsActive.Value
int? CategoryId x => x.CategoryId == CategoryId.Value
decimal? MinPrice x => x.Price >= MinPrice.Value
decimal? MaxPrice x => x.Price <= MaxPrice.Value
DateTime? CreatedFrom x => x.Created >= CreatedFrom.Value
DateTime? CreatedTo x => 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))]
public partial class ProductQuery
{
    public string? Name { get; set; }
    public decimal? Price { get; set; }
    [QuerySort] public string? SortBy { get; set; }
    public bool SortDescending { get; set; }
}

Generated shape:

if (SortBy is 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))]
public partial class ProductQuery
{
    public string? Name { get; set; }
    public int PageNumber { get; set; } = 1;
    public int PageSize { get; set; } = 20;
}

HTTP query-string binding

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

public void BindFromQuery(IEnumerable<KeyValuePair<string, string?>> query);
public void BindFromQuery<TValue>(IEnumerable<KeyValuePair<string, TValue>> query)
    where TValue : IEnumerable<string>;

public static ProductQuery FromQuery(IEnumerable<KeyValuePair<string, string?>> query);
public static ProductQuery FromQuery<TValue>(IEnumerable<KeyValuePair<string, TValue>> query)
    where TValue : 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

Id Severity Description
AQ001 Error [QuerySpec] entity type could not be resolved.
AQ002 Error [QuerySpec] target class must be declared partial.
AQ003 Warning Spec has no filterable properties.

Comparison

Capability AutoQuery.Generator Manual LINQ Ardalis.Specification
Compile-time generated Apply method โœ… โŒ โŒ
Compile-time generated query-string binding โœ… โŒ โŒ
Reflection-free โœ… โœ… Usually โœ…
Convention filters from DTO-like class โœ… โŒ โŒ
Custom inline filter expressions โœ… Manual only Via handwritten spec logic
Built-in sort switch generation โœ… Manual only Manual only
Built-in pagination generation โœ… Manual only Manual only
Runtime abstraction dependency None None Package dependency

Also by the same author

๐ŸŒ Full suite overview: swevo.github.io

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

License

MIT.

There are no supported framework assets in this package.

Learn more about Target Frameworks and .NET Standard.

  • .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.1.0 100 7/10/2026
1.0.1 105 7/7/2026
1.0.0 112 6/25/2026

Added generated FromQuery/BindFromQuery helpers for query-string binding without an ASP.NET Core runtime dependency.