Your endpoint returns 200 OK. It also ran the same query 51 times.
QueryGuard counts the EF Core queries that your code runs, groups repeated SQL, and lets a test fail before an N+1 regression reaches production.
It reports repeated-query candidates, not proof. Some repetition is correct. Intentional cases stay visible with a written reason.
For an ASP.NET Core integration test, install the WebApplicationFactory helper:
dotnet add package QueryGuard.AspNetCore.TestingOpen a measurement, run the request, and assert the result:
using var factory = new WebApplicationFactory<Program>();
await using var guard = factory.TrackQueries<Program, AppDbContext>(
"GET /api/companies",
QueryGuardPolicy.Create("companies")
.WithMaxOccurrencesPerFingerprint(5));
var response = await guard.Client.GetAsync("/api/companies");
response.EnsureSuccessStatusCode();
QueryGuardAssert.Passes(await guard.CompleteAsync());The helper attaches QueryGuard to AppDbContext, preserves the test execution context, and prevents
request middleware from opening a second scope. It has no test framework dependency, so it works with
xUnit, NUnit, MSTest, and TUnit.
Testing a service or background job without WebApplicationFactory? Install QueryGuard.Testing,
attach .UseQueryGuard() where the context is configured, and open an explicit QueryGuardScope.
See the testing guide for both paths.
| Package | Use it for |
|---|---|
QueryGuard.AspNetCore.Testing |
Measuring real WebApplicationFactory requests |
QueryGuard.Testing |
Explicit scopes and assertions around services or jobs |
QueryGuard.AspNetCore |
Request middleware and route policies |
QueryGuard.Reporting |
Console, JSON, JUnit, Markdown, and SARIF output |
QueryGuard.Cli |
Recording and checking baselines in CI |
QueryGuard FAILED: GET /api/companies (policy 'companies')
51 read queries in 2 distinct queries
[FAIL] max-occurrences-per-fingerprint: QG-FP-FDB5F469 executed 50 times; the budget is 5.
SQL: SELECT COUNT(*) FROM "Departments" AS "d" WHERE "d"."CompanyId" = ?
origin: samples/QueryGuard.SampleApi/Program.cs:line 89
The report shows the repeated fingerprint, normalized SQL, and the application line that ran it. Stack traces are captured once per distinct query in explicit test scopes. They stay off by default on request paths because they are much more expensive than normal capture.
If you do not know the right budget yet, record current behavior and compare it in CI:
dotnet tool install -g QueryGuard.Cli
queryguard baseline record
queryguard verify --summary artifacts/queryguard/summary.mdAdd --fail-on-regression when a regression should fail the build. Without it, the tool reports the
change and exits successfully.
Publish the Markdown table as a job summary and sticky pull request comment:
- uses: Benziza/queryguard-dotnet@v0.1.0
with:
summary-path: artifacts/queryguard/summary.mdSee the baseline guide and action guide.
| Component | Support |
|---|---|
| .NET | .NET 8 and .NET 10 |
| EF Core | EF Core 8 and EF Core 10 |
| Providers tested with real databases | SQLite, PostgreSQL, SQL Server, MySQL |
| Other providers | Any relational EF Core provider through DbCommandInterceptor |
MySQL tests use Oracle's MySql.EntityFrameworkCore. Pomelo does not have an EF Core 10 release yet.
See provider support for the exact claim and current caveats.
0.1.0-preview.6 was added to three public ASP.NET Core test suites before the stable release:
| Project | Test stack | Request | Result |
|---|---|---|---|
| CleanArchitecture | NUnit, SQLite | POST /api/Users/register |
1 query, 1 group |
| SSW.VerticalSliceArchitecture | xUnit, SQL Server | GET /api/heroes |
2 queries, 2 groups |
| CleanArchitecture | NUnit, SQL Server | GET /api/v1/Tenant/{id} |
2 queries, 2 groups |
All three request tests passed with no repeated-query finding. The validation notes include the setup, the package compatibility problem the work found, and the limits of this check.
QueryGuard is focused on query-count regressions.
- It does not prove an N+1.
- It does not observe Dapper or raw ADO.NET.
- It does not collect execution plans or provide a profiler UI.
- It does not rewrite queries or change HTTP responses.
- The
0.1public API is stable within the0.1release line. The project has not reached1.0.0.
Parameter values and connection strings are not captured. Redaction runs before any reporter receives
SQL. The JSON report has a schemaVersion so format changes are explicit.
git clone https://github.com/Benziza/queryguard-dotnet.git
cd queryguard-dotnet
dotnet test samples/QueryGuard.SampleTestsThe sample includes a 51-query endpoint, a one-query fix, a baseline comparison, and an intentional repetition with an allowlist reason.
Full documentation and the generated API reference are available at benziza.github.io/queryguard-dotnet.
| Guide | Covers |
|---|---|
| How it works | Sessions, fingerprints, redaction, analysis |
| Testing | WebApplicationFactory requests and explicit scopes |
| Public validation | Results from three public ASP.NET Core projects |
| Configuration | Budgets and defaults |
| Baselines | Recording and comparing query behavior |
| Troubleshooting | Missing capture, grouping, middleware order |
| False positives | Allowlisting with a reason |
| Benchmarks | Methodology and raw output |
| Decision records | Design decisions and tradeoffs |
| API reference | Every public type |
Issues and focused pull requests are welcome. Small fixes can go straight to a pull request. Open an issue first for public API, capture, privacy, or detector changes. See CONTRIBUTING.md.
False-positive reports are especially useful because they improve the defaults and become regression fixtures. Report security issues through SECURITY.md, not a public issue.
MIT. The project takes product inspiration from Bullet for Rails. The implementation is independent and uses EF Core's public interception API.