Skip to main content

Working query in 10 minutes.

Add the package, define what's queryable, accept a JSON payload, call Execute(). That's it.

Install via NuGet

Supports .NET 6, 7, 8, 9, and 10

.NET CLI
dotnet add package ServiceQuery
Package Manager
Install-Package ServiceQuery
PackageReference
<PackageReference 
    Include="ServiceQuery" />

Your First Query

ServiceQuery works with any IQueryable — database tables, collections, or in-memory lists.

The Mental Model

You give ServiceQuery an IQueryable<T>. It returns results after applying the request's filter/sort/page/aggregate. No magic. No hidden schema.

using ServiceQuery;

// Get your database table as an IQueryable
var queryable = databaseContext.Products.AsQueryable();

// Or use in-memory lists
var list = new List<Product>
{
    new Product { Id = 1, Name = "Widget", Price = 9.99m }
}.AsQueryable();

// Build a request
var request = new ServiceQueryRequestBuilder().Build();

// Execute and get results
var response = request.Execute(queryable);

// Async support
var responseAsync = await request.ExecuteAsync(queryable);

Use in a REST API

Add a query endpoint to any controller in just a few lines.

Minimal API
app.MapPost("/products/search", 
    (ServiceQueryRequest request, AppDb db) =>
{
    var queryable = db.Products.AsQueryable();
    return Results.Ok(request.Execute(queryable));
});
Controller
[HttpPost("query")]
public ServiceQueryResponse<ProductDto> Query(
    ServiceQueryRequest request)
{
    var queryable = _db.Products.AsQueryable();
    return request.Execute(queryable);
}

JavaScript Client

Same builder pattern works in JavaScript. Include servicequery.js in your project.

Using Fetch
const request = new ServiceQueryRequestBuilder()
    .IsEqual("Status", "Active")
    .SortDesc("CreatedAt")
    .Paging(1, 25)
    .Build();

fetch('/api/products/query', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(request)
})
.then(response => response.json())
.then(result => {
    console.log(result.list.length + ' records');
});
Using jQuery
const request = new ServiceQueryRequestBuilder()
    .IsEqual("Status", "Active")
    .SortDesc("CreatedAt")
    .Paging(1, 25)
    .Build();

$.ajax({
    url: '/api/products/query',
    type: 'POST',
    dataType: 'json',
    contentType: 'application/json',
    data: JSON.stringify(request),
    success: function(result) {
        console.log(result.list.length + ' records');
    }
});

Building Queries

The same code works in .NET or JavaScript. Here are common patterns:

// Default - returns all records with paging
var request = new ServiceQueryRequestBuilder().Build();

// Paging
request = new ServiceQueryRequestBuilder()
    .Paging(1, 25, true)  // page 1, 25 per page, include count
    .Build();

// Aggregates (return aggregate values, not records)
request = new ServiceQueryRequestBuilder().Count().Build();
request = new ServiceQueryRequestBuilder().Sum("Price").Build();
request = new ServiceQueryRequestBuilder().Average("Price").Build();
request = new ServiceQueryRequestBuilder().Minimum("Price").Build();
request = new ServiceQueryRequestBuilder().Maximum("Price").Build();

// Select specific properties
request = new ServiceQueryRequestBuilder()
    .Select("Id", "Name", "Price")
    .Build();

// Filtering
request = new ServiceQueryRequestBuilder()
    .IsEqual("Status", "Active")
    .Build();

request = new ServiceQueryRequestBuilder()
    .IsGreaterThan("Price", "100")
    .Build();

request = new ServiceQueryRequestBuilder()
    .Contains("Name", "widget")
    .Build();

request = new ServiceQueryRequestBuilder()
    .IsInSet("Status", "Open", "Pending", "InProgress")
    .Build();

// AND / OR logic
request = new ServiceQueryRequestBuilder()
    .IsEqual("Status", "Active")
    .And()
    .IsGreaterThan("Price", "50")
    .Build();

// Grouped expressions with Begin() and End()
request = new ServiceQueryRequestBuilder()
    .Begin()
        .IsEqual("Status", "Active")
        .Or()
        .IsEqual("Status", "Pending")
    .End()
    .And()
    .IsGreaterThan("Amount", "100")
    .Build();

// Sorting
request = new ServiceQueryRequestBuilder()
    .SortAsc("Category")
    .SortDesc("Price")
    .Build();

Async Support

Async execution requires provider-specific packages:

ServiceQuery.EntityFrameworkCore

For EF Core databases (SQL Server, PostgreSQL, MySQL, SQLite). Targets EF Core 9 / .NET 8+

ServiceQuery.EntityFrameworkCore8

For EF Core 8 / .NET 8 projects.

ServiceQuery.MongoDb

For MongoDB. Uses MongoDbExecuteAsync().

ServiceQuery.AzureDataTables

For Azure Data Tables. Includes workarounds for unsupported features.

Before You Ship

Start with paging limits. Don't ship a public endpoint with unlimited page size and then blame the query library when your database cries.

Review Security & Guardrails