📖 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);dotnet add package AutoQuery.GeneratorTargets netstandard2.0 and works with modern SDK-style .NET projects.
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; }
}IQueryable<Product> query = dbContext.Products;
var spec = new ProductQuery { Name = "Phone", MinPrice = 100, IsActive = true };
var result = spec.Apply(query);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;
}
}Marks a partial class as a query spec for the target entity.
[QuerySpec(typeof(Order))]
public partial class OrderQuery { }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; }Skips a property entirely.
[QueryIgnore]
public string? DebugOnly { get; set; }Marks a string? property as the requested sort field.
[QuerySort]
public string? SortBy { get; set; }
public bool SortDescending { get; set; }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;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→<=
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
};
}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;
}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.
| 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. |
| 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 |
🌐 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. |
| Package | Downloads | Description |
|---|---|---|
| AutoWire | Compile-time dependency injection auto-registration for | |
| AutoMap.Generator | Compile-time object mapping for | |
| AutoArchitecture | Compile-time architecture/dependency-rule enforcement for | |
| AutoHttpClient.Generator | Compile-time typed HTTP client generation for | |
| AutoDispatch.Generator | Compile-time CQRS dispatcher for | |
| AutoLog.Generator | Compile-time high-performance logging for | |
| AutoValidate.Generator | Compile-time FluentValidation wiring for |
MIT.