Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Latest commit

 

History

14 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

AutoQuery.Generator

NuGet NuGet Downloads CI License: 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.

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.

Related Packages

Package Downloads Description
AutoWire Downloads Compile-time dependency injection auto-registration for
AutoMap.Generator Downloads Compile-time object mapping for
AutoArchitecture Downloads Compile-time architecture/dependency-rule enforcement for
AutoHttpClient.Generator Downloads Compile-time typed HTTP client generation for
AutoDispatch.Generator Downloads Compile-time CQRS dispatcher for
AutoLog.Generator Downloads Compile-time high-performance logging for
AutoValidate.Generator Downloads Compile-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