⚡ Roslyn source generator · strongly-typed Apply(IQueryable<T>) · zero reflection · AOT-safe

AutoQuery.Generator

Compile-time LINQ query specs for .NET

$ dotnet add package AutoQuery.Generator
your query spec
using AutoQuery;

[QuerySpec(typeof(Product))]
public partial class ProductQuery
{
    public string? Name { get; set; }
    public decimal? MinPrice { get; set; }
    public bool? IsActive { get; set; }
}
generated at build time
// ✨ ProductQuery.AutoQuery.g.cs
public partial class ProductQuery
{
    public IQueryable<Product> Apply(IQueryable<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;
    }
}

Everything you need

Define a partial spec once and let compile-time conventions generate the LINQ for filtering, paging, and sorting.

🧠

Convention Filters

string? becomes Contains, nullable value types become equality checks, and range naming like MinPrice is generated automatically.

📄

Pagination Built-In

Add PageNumber and PageSize and AutoQuery emits the Skip().Take() pipeline for you.

↕️

Sort Switch

A [QuerySort] string plus SortDescending generates a compile-time switch expression for ordering.

✍️

Custom Expressions

Use [QueryFilter("x.Price > MinPrice")] when you want an inline predicate instead of the default property convention.

🚫

Zero Reflection

All LINQ is emitted at build time. No runtime reflection, no runtime expression building, and no dynamic behavior that breaks AOT.

🛡️

Roslyn Diagnostics

AQ001, AQ002, and AQ003 catch generator misuse while you build — not after you deploy.

Examples

From simple filters to paging, sorting, and custom predicates.

using AutoQuery;

[QuerySpec(typeof(Product))]
public partial class ProductQuery
{
    public string? Name { get; set; }       // → .Where(x => x.Name.Contains(Name))
    public decimal? MinPrice { get; set; }  // → .Where(x => x.MinPrice >= MinPrice)
    public bool? IsActive { get; set; }     // → .Where(x => x.IsActive == IsActive)
}

// Usage:
var query = new ProductQuery { Name = "Widget", IsActive = true };
var results = await dbContext.Products
    .Apply(query)
    .ToListAsync();
// Auto-generated by AutoQuery.Generator
public partial class ProductQuery
{
    public IQueryable<Product> Apply(IQueryable<Product> source)
    {
        if (Name is not null)
            source = source.Where(x => x.Name.Contains(Name));
        if (MinPrice is not null)
            source = source.Where(x => x.Price >= MinPrice.Value);
        if (IsActive is not null)
            source = source.Where(x => x.IsActive == IsActive.Value);
        return source;
    }
}
[QuerySpec(typeof(Product))]
public partial class ProductQuery
{
    public string? Name { get; set; }

    [QuerySort]
    public string? SortBy { get; set; }     // → switch expression on column name
    public bool SortDescending { get; set; }

    public int PageNumber { get; set; } = 1;
    public int PageSize { get; set; } = 20; // → .Skip((PageNumber-1)*PageSize).Take(PageSize)
}
[QuerySpec(typeof(Product))]
public partial class ProductQuery
{
    public decimal? MinPrice { get; set; }
    public decimal? MaxPrice { get; set; }

    [QueryFilter("x.Price >= MinPrice")]
    public decimal? PriceFrom { get; set; }

    [QueryFilter("x.Price <= MaxPrice")]
    public decimal? PriceTo { get; set; }

    [QueryIgnore]
    public string? InternalNotes { get; set; } // excluded from Apply()
}

Build-time diagnostics

Misuse is surfaced by the compiler before your query ever runs.

Code Severity Description
AQ001 ⛔ Error [QuerySpec] applied to a non-partial class
AQ002 ⚠ Warning [QuerySpec] class has no filterable properties
AQ003 ⚠ Warning [QueryFilter] expression is blank

Comparison

AutoQuery.Generator keeps the simplicity of plain LINQ while removing repetitive boilerplate.

Feature AutoQuery.Generator Manual LINQ Ardalis.Specification
Compile-time generated Apply
Convention filters from DTO properties
Zero reflection Usually ✅
Built-in pagination generation Manual Manual
Built-in sort switch Manual Manual
AOT-safe
Runtime abstraction dependency None None Package dependency

Also by the same author

More compile-time tooling for .NET from Justin Bannister.