First Query
This walkthrough builds a production-shaped ASP.NET Core endpoint that accepts dynamic query parameters and executes them against Entity Framework Core. It takes about five minutes, and everything you learn here composes with the rest of the documentation.
What you are building
One endpoint that handles, with no additional code:
GET /api/customers?filter=Status:eq:Active&sort=LastName:asc&page=1&pageSize=20&select=Id,FirstName,Email…as a server-side, validated, SQL-translated query — not in-memory LINQ.
0. Prerequisites
- .NET 6, 8, or 10 project with EF Core set up and a
Customerentity on aDbContext. - The packages installed:
dotnet add package FlexQuery.NET
dotnet add package FlexQuery.NET.EntityFrameworkCore1. Configure global options at startup
Call FlexQueryCore.Configure once in Program.cs, before any query executes:
using FlexQuery.NET;
var builder = WebApplication.CreateBuilder(args);
FlexQueryCore.Configure(options =>
{
options.DefaultPageSize = 20;
options.MaxPageSize = 1000;
options.StrictFieldValidation = true;
});
builder.Services.AddControllers();These are defaults, not security — endpoints override them per request below.
2. Create the endpoint
FlexQueryParameters is the model binder for query-string input. Pass it straight to
FlexQueryAsync:
using FlexQuery.NET;
using FlexQuery.NET.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
[ApiController]
[Route("api/customers")]
public sealed class CustomersController(AppDbContext db) : ControllerBase
{
[HttpGet]
public async Task<IActionResult> GetCustomers(
[FromQuery] FlexQueryParameters parameters,
CancellationToken cancellationToken)
{
var result = await db.Customers
.AsNoTracking()
.FlexQueryAsync(parameters, opt =>
{
opt.AllowedFields = ["Id", "FirstName", "LastName", "Email", "Status"];
opt.MaxPageSize = 100;
opt.DefaultSortField = "Id";
}, cancellationToken);
return Ok(result);
}
}What each line buys you:
AsNoTracking()— read-only queries without change-tracking overhead.AllowedFields— the only fields clients can filter, sort, or select on.MaxPageSize— an endpoint-level ceiling (clients cannot exceed it).DefaultSortField— stable page boundaries even when clients omitsort.
3. Query it
GET /api/customers?filter=Status:eq:Active&sort=LastName:asc&page=1&pageSize=20&select=Id,FirstName,EmailResponse (ASP.NET Core's default camelCase JSON):
{
"data": [
{ "id": 3, "firstName": "Ana", "email": "ana@example.com" }
],
"totalCount": 42,
"page": 1,
"pageSize": 20,
"totalPages": 3,
"hasNextPage": true,
"hasPreviousPage": false
}Only the selected fields appear on each row — with an explicit select, the result-shape
converter emits exactly the requested surface and nothing else.
What just happened
FlexQueryParametersbound the query string (filter,sort,page,pageSize,select).FlexQueryAsyncparsed the parameters with the default syntax (NativeDsl), validated every field againstAllowedFields, and applied the pipeline — filter → sort → paging → projection — as SQL-translated expression trees.- Only selected columns left the database, and only allowed fields could be addressed.
Variations
Explicit query options
When the query is composed server-side instead of parsed from the request:
var result = await db.Customers
.AsNoTracking()
.FlexQueryAsync(
new QueryOptions
{
Sort = [new SortNode { Field = "LastName", Descending = false }],
Paging = new PagingOptions { Page = 1, PageSize = 20 },
},
cancellationToken: cancellationToken);QueryOptions lives in FlexQuery.NET.Models; SortNode and PagingOptions as well.
Typed DTO result
Project into your own response type — same endpoint shape, documented contract:
public record CustomerDto(int Id, string Name, string Email);
var result = await db.Customers
.AsNoTracking()
.FlexQueryAsync<Customer, CustomerDto>(parameters, cancellationToken: cancellationToken);See Typed DTO Projection for the mapping model
(CreateMap, ForMember, ForNavigation).
Alternative query syntax
Clients can address the same endpoint with FQL or MiniOData once the parser packages are installed and registered:
Fql.Register(); // FlexQuery.NET.Parsers.Fql
MiniOData.Register(); // FlexQuery.NET.Parsers.MiniODataGET /api/customers?filter=Status = 'Active' (FQL)See Query Syntax.
Next steps
- Configuration — the three configuration levels.
- Filtering — the full operator reference.
- Paging — offset vs keyset modes.
- Security & Governance — locking endpoints down properly.