Latest commit

History

97 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

QueryWatch

QueryWatch is a .NET library for catching database-query regressions in tests and CI before they reach production. It records executed SQL, counts queries, measures timings, exports JSON summaries, and lets you fail builds on budget violations.

Works with:

  • ADO.NET
  • Dapper
  • EF Core

Why Use It

QueryWatch is designed for test-time guardrails, not production profiling dashboards.

Typical use cases:

  • Catch N+1 regressions introduced by ORM changes
  • Enforce per-test query-count budgets
  • Fail CI when average or total SQL time drifts upward
  • Export machine-readable summaries for baselines and PR reporting
  • Capture parameter shape metadata without storing parameter values

Packages

PackagePurpose
KeelMatrix.QueryWatchCore recording, assertions, JSON export, ADO.NET and Dapper wrapping
KeelMatrix.QueryWatch.EfCoreEF Core interceptor and UseQueryWatch(...) integration
qwatch.NET tool for enforcing query and SQL performance budgets in CI

Install

Core only:

dotnet add package KeelMatrix.QueryWatch

EF Core integration:

dotnet add package KeelMatrix.QueryWatch
dotnet add package KeelMatrix.QueryWatch.EfCore

Optional redaction helpers:

dotnet add package KeelMatrix.Redaction

QueryWatch restores KeelMatrix.Redaction and KeelMatrix.Telemetry 0.1.0 from NuGet.org. The sample helper uses a local feed only for QueryWatch packages built from this repository.

Install the public CI tool:

dotnet tool install --global qwatch --version 0.1.0

5-Minute Quick Start

usingKeelMatrix.QueryWatch;usingKeelMatrix.QueryWatch.Assertions;usingKeelMatrix.QueryWatch.Reporting;usingQueryWatchSessionsession=new();usingvarconn=rawConnection.WithQueryWatch(session);// Run code that talks to the database.QueryWatchReportreport=session.Complete();report.ShouldHaveExecutedAtMost(20);report.ShouldHaveMaxAverageTime(TimeSpan.FromMilliseconds(15));QueryWatchJson.ExportToFile(report,"artifacts/qwatch.report.json",sampleTop:200);

The exported JSON can be consumed by the CLI in CI.

Real-World Scenarios

Prevent accidental N+1 queries

Wrap the test scope, execute the application code, and assert the query count stays below a fixed threshold.

Gate pull requests on SQL budgets

Export a summary file during tests, then run the CLI in GitHub Actions to fail the build if query counts or timings regress.

Track parameter shape safely

Enable parameter-shape capture to understand whether code is issuing parameterized commands, without persisting sensitive parameter values.

Normalize SQL before comparisons

Add redactors to remove secrets, GUID noise, timestamps, or tokens so CI diffs focus on structural query changes.

Quick Start - Samples (Local)

This repo ships three sample apps that consume local packages built from source. The helper scripts pack QueryWatch core and EF Core locally; Redaction and Telemetry restore from NuGet.org.

  1. Build and pack the local packages used by the samples:

    • PowerShell: pwsh -NoProfile -File build/Dev-PackInstallSamples.ps1
    • bash: bash build/Dev-PackInstallSamples.sh
  2. Run a sample:

    dotnet run --project ./samples/EFCore.Sqlite/EFCore.Sqlite.csproj -c Release
  3. Gate the generated summary with the CLI:

    dotnet run --project ./tools/KeelMatrix.QueryWatch.Cli -- --input ./samples/EFCore.Sqlite/bin/Release/net8.0/artifacts/qwatch.ef.json --max-queries 50

EF Core Wiring

usingKeelMatrix.QueryWatch;usingKeelMatrix.QueryWatch.EfCore;usingKeelMatrix.QueryWatch.Reporting;usingvarsession=newQueryWatchSession();varoptions=newDbContextOptionsBuilder<MyDbContext>().UseSqlite("Data Source=:memory:").UseQueryWatch(session).Options;// Run workload...varreport=session.Complete();QueryWatchJson.ExportToFile(report,"artifacts/ef.json",sampleTop:200);

The EF Core interceptor records executed commands only. Use QueryWatchOptions to tune SQL text capture, sampling, and parameter-shape capture.

Dapper Wiring

usingKeelMatrix.QueryWatch;usingKeelMatrix.QueryWatch.Reporting;usingvarsession=newQueryWatchSession();awaitusingvarraw=newSqliteConnection("Data Source=:memory:");awaitraw.OpenAsync();usingvarconn=raw.WithQueryWatch(session);varrows=awaitconn.QueryAsync("SELECT 1");varreport=session.Complete();QueryWatchJson.ExportToFile(report,"artifacts/dapper.json",sampleTop:200);

If the underlying connection is a DbConnection, QueryWatch uses the higher-fidelity ADO.NET wrapper automatically.

ADO.NET Wiring

usingKeelMatrix.QueryWatch;usingKeelMatrix.QueryWatch.Reporting;usingvarsession=newQueryWatchSession();awaitusingvarraw=newSqliteConnection("Data Source=:memory:");awaitraw.OpenAsync();usingvarconn=raw.WithQueryWatch(session);usingvarcmd=conn.CreateCommand();cmd.CommandText="SELECT 1";awaitcmd.ExecuteNonQueryAsync();varreport=session.Complete();QueryWatchJson.ExportToFile(report,"artifacts/ado.json",sampleTop:200);

Redaction

If captured SQL can include secrets, tokens, email addresses, or noisy identifiers, add KeelMatrix.Redaction and configure redactors on QueryWatchOptions.

usingKeelMatrix.QueryWatch;usingKeelMatrix.QueryWatch.Redaction;usingKeelMatrix.Redaction;varoptions=newQueryWatchOptions().UseRecommendedRedactors(includeTimestamps:false,includeIpAddresses:false,includePhone:false);

Budgets

At test time, enforce budgets directly on QueryWatchReport.

At CI time, use the CLI for:

  • total-query budgets
  • average-duration budgets
  • total-duration budgets
  • baseline comparisons
  • per-pattern budgets

Per-pattern budgets support wildcards (*, ?) or a regex: prefix.

Examples:

--budget "SELECT * FROM Users*=1"
--budget "regex:^UPDATE Orders SET=3"

If a summary is top-N sampled, budgets are evaluated only over those captured events. Increase sampleTop if you need stricter guarantees.

CLI

Usage:
qwatch --input file.json [options]
qwatch telemetry <status|disable|enable> [options]
Commands:
telemetry status [--json] Show effective telemetry state and repo-local config status for the current repo.
telemetry disable Write a qwatch-managed repo-local telemetry opt-out.
telemetry enable Remove or neutralize qwatch-managed repo-local telemetry opt-out.
Options:
--input <path> Input JSON summary file. (repeatable)
--max-queries N Fail if total query count exceeds N.
--max-average-ms N Fail if average duration exceeds N ms.
--max-total-ms N Fail if total duration exceeds N ms.
--baseline <path> Baseline summary JSON to compare against.
--baseline-allow-percent P Allow +P% regression vs baseline before failing.
--write-baseline Write current aggregated summary to --baseline.
--budget "<pattern>=<max>" Per-pattern query count budget. (repeatable)
Pattern supports wildcards (*, ?) or prefix with 'regex:' for raw regex.
--require-full-events Fail if input summaries are top-N sampled.
--help Show this help.

Multi-file support:

  • repeat --input to aggregate summaries from multiple test projects
  • compare current results against a baseline summary
  • write GitHub Actions step summaries automatically when running in CI
  • inspect or manage repo-local telemetry opt-out state with qwatch telemetry status|disable|enable

Troubleshooting

  • Pattern budgets look incomplete: your summary may be sampled too aggressively. Re-export with a higher sampleTop.
  • Baseline checks are noisy: use --baseline-allow-percent and keep baselines representative.
  • CLI flags in the README look stale: refresh the generated block with build/Update-ReadmeFlags.ps1.
  • You do not want SQL text on a hot path: set QueryWatchOptions.CaptureSqlText = false.
  • You want metadata without secret leakage: use parameter-shape capture, not parameter-value capture.

Privacy

QueryWatch uses KeelMatrix.Telemetry transitively for minimal anonymous usage telemetry.

See:

For the CLI, qwatch telemetry disable writes a repo-local opt-out file and qwatch telemetry enable removes or neutralizes only qwatch-managed repo-local opt-out state. QueryWatch-owned files use managedBy: "qwatch" as the ownership marker. Higher-precedence process environment variables still win, and existing non-qwatch-managed repo-local config is left untouched.

License

MIT

About

QueryWatch – lightweight .NET library and CLI to catch N+1 queries and slow SQL in your tests; enforce query count and timing budgets, export JSON, and plug directly into CI/CD to fail builds before regressions reach production.

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all \u003cpre\u003e\u003ccode\u003e blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks"); } } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); } })(); (function(){ try { var __m = "github.com"; var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Latest commit

History

97 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

QueryWatch

QueryWatch is a .NET library for catching database-query regressions in tests and CI before they reach production. It records executed SQL, counts queries, measures timings, exports JSON summaries, and lets you fail builds on budget violations.

Works with:

  • ADO.NET
  • Dapper
  • EF Core

Why Use It

QueryWatch is designed for test-time guardrails, not production profiling dashboards.

Typical use cases:

  • Catch N+1 regressions introduced by ORM changes
  • Enforce per-test query-count budgets
  • Fail CI when average or total SQL time drifts upward
  • Export machine-readable summaries for baselines and PR reporting
  • Capture parameter shape metadata without storing parameter values

Packages

PackagePurpose
KeelMatrix.QueryWatchCore recording, assertions, JSON export, ADO.NET and Dapper wrapping
KeelMatrix.QueryWatch.EfCoreEF Core interceptor and UseQueryWatch(...) integration
qwatch.NET tool for enforcing query and SQL performance budgets in CI

Install

Core only:

dotnet add package KeelMatrix.QueryWatch

EF Core integration:

dotnet add package KeelMatrix.QueryWatch
dotnet add package KeelMatrix.QueryWatch.EfCore

Optional redaction helpers:

dotnet add package KeelMatrix.Redaction

QueryWatch restores KeelMatrix.Redaction and KeelMatrix.Telemetry 0.1.0 from NuGet.org. The sample helper uses a local feed only for QueryWatch packages built from this repository.

Install the public CI tool:

dotnet tool install --global qwatch --version 0.1.0

5-Minute Quick Start

usingKeelMatrix.QueryWatch;usingKeelMatrix.QueryWatch.Assertions;usingKeelMatrix.QueryWatch.Reporting;usingQueryWatchSessionsession=new();usingvarconn=rawConnection.WithQueryWatch(session);// Run code that talks to the database.QueryWatchReportreport=session.Complete();report.ShouldHaveExecutedAtMost(20);report.ShouldHaveMaxAverageTime(TimeSpan.FromMilliseconds(15));QueryWatchJson.ExportToFile(report,"artifacts/qwatch.report.json",sampleTop:200);

The exported JSON can be consumed by the CLI in CI.

Real-World Scenarios

Prevent accidental N+1 queries

Wrap the test scope, execute the application code, and assert the query count stays below a fixed threshold.

Gate pull requests on SQL budgets

Export a summary file during tests, then run the CLI in GitHub Actions to fail the build if query counts or timings regress.

Track parameter shape safely

Enable parameter-shape capture to understand whether code is issuing parameterized commands, without persisting sensitive parameter values.

Normalize SQL before comparisons

Add redactors to remove secrets, GUID noise, timestamps, or tokens so CI diffs focus on structural query changes.

Quick Start - Samples (Local)

This repo ships three sample apps that consume local packages built from source. The helper scripts pack QueryWatch core and EF Core locally; Redaction and Telemetry restore from NuGet.org.

  1. Build and pack the local packages used by the samples:

    • PowerShell: pwsh -NoProfile -File build/Dev-PackInstallSamples.ps1
    • bash: bash build/Dev-PackInstallSamples.sh
  2. Run a sample:

    dotnet run --project ./samples/EFCore.Sqlite/EFCore.Sqlite.csproj -c Release
  3. Gate the generated summary with the CLI:

    dotnet run --project ./tools/KeelMatrix.QueryWatch.Cli -- --input ./samples/EFCore.Sqlite/bin/Release/net8.0/artifacts/qwatch.ef.json --max-queries 50

EF Core Wiring

usingKeelMatrix.QueryWatch;usingKeelMatrix.QueryWatch.EfCore;usingKeelMatrix.QueryWatch.Reporting;usingvarsession=newQueryWatchSession();varoptions=newDbContextOptionsBuilder<MyDbContext>().UseSqlite("Data Source=:memory:").UseQueryWatch(session).Options;// Run workload...varreport=session.Complete();QueryWatchJson.ExportToFile(report,"artifacts/ef.json",sampleTop:200);

The EF Core interceptor records executed commands only. Use QueryWatchOptions to tune SQL text capture, sampling, and parameter-shape capture.

Dapper Wiring

usingKeelMatrix.QueryWatch;usingKeelMatrix.QueryWatch.Reporting;usingvarsession=newQueryWatchSession();awaitusingvarraw=newSqliteConnection("Data Source=:memory:");awaitraw.OpenAsync();usingvarconn=raw.WithQueryWatch(session);varrows=awaitconn.QueryAsync("SELECT 1");varreport=session.Complete();QueryWatchJson.ExportToFile(report,"artifacts/dapper.json",sampleTop:200);

If the underlying connection is a DbConnection, QueryWatch uses the higher-fidelity ADO.NET wrapper automatically.

ADO.NET Wiring

usingKeelMatrix.QueryWatch;usingKeelMatrix.QueryWatch.Reporting;usingvarsession=newQueryWatchSession();awaitusingvarraw=newSqliteConnection("Data Source=:memory:");awaitraw.OpenAsync();usingvarconn=raw.WithQueryWatch(session);usingvarcmd=conn.CreateCommand();cmd.CommandText="SELECT 1";awaitcmd.ExecuteNonQueryAsync();varreport=session.Complete();QueryWatchJson.ExportToFile(report,"artifacts/ado.json",sampleTop:200);

Redaction

If captured SQL can include secrets, tokens, email addresses, or noisy identifiers, add KeelMatrix.Redaction and configure redactors on QueryWatchOptions.

usingKeelMatrix.QueryWatch;usingKeelMatrix.QueryWatch.Redaction;usingKeelMatrix.Redaction;varoptions=newQueryWatchOptions().UseRecommendedRedactors(includeTimestamps:false,includeIpAddresses:false,includePhone:false);

Budgets

At test time, enforce budgets directly on QueryWatchReport.

At CI time, use the CLI for:

  • total-query budgets
  • average-duration budgets
  • total-duration budgets
  • baseline comparisons
  • per-pattern budgets

Per-pattern budgets support wildcards (*, ?) or a regex: prefix.

Examples:

--budget "SELECT * FROM Users*=1"
--budget "regex:^UPDATE Orders SET=3"

If a summary is top-N sampled, budgets are evaluated only over those captured events. Increase sampleTop if you need stricter guarantees.

CLI

Usage:
qwatch --input file.json [options]
qwatch telemetry <status|disable|enable> [options]
Commands:
telemetry status [--json] Show effective telemetry state and repo-local config status for the current repo.
telemetry disable Write a qwatch-managed repo-local telemetry opt-out.
telemetry enable Remove or neutralize qwatch-managed repo-local telemetry opt-out.
Options:
--input <path> Input JSON summary file. (repeatable)
--max-queries N Fail if total query count exceeds N.
--max-average-ms N Fail if average duration exceeds N ms.
--max-total-ms N Fail if total duration exceeds N ms.
--baseline <path> Baseline summary JSON to compare against.
--baseline-allow-percent P Allow +P% regression vs baseline before failing.
--write-baseline Write current aggregated summary to --baseline.
--budget "<pattern>=<max>" Per-pattern query count budget. (repeatable)
Pattern supports wildcards (*, ?) or prefix with 'regex:' for raw regex.
--require-full-events Fail if input summaries are top-N sampled.
--help Show this help.

Multi-file support:

  • repeat --input to aggregate summaries from multiple test projects
  • compare current results against a baseline summary
  • write GitHub Actions step summaries automatically when running in CI
  • inspect or manage repo-local telemetry opt-out state with qwatch telemetry status|disable|enable

Troubleshooting

  • Pattern budgets look incomplete: your summary may be sampled too aggressively. Re-export with a higher sampleTop.
  • Baseline checks are noisy: use --baseline-allow-percent and keep baselines representative.
  • CLI flags in the README look stale: refresh the generated block with build/Update-ReadmeFlags.ps1.
  • You do not want SQL text on a hot path: set QueryWatchOptions.CaptureSqlText = false.
  • You want metadata without secret leakage: use parameter-shape capture, not parameter-value capture.

Privacy

QueryWatch uses KeelMatrix.Telemetry transitively for minimal anonymous usage telemetry.

See:

For the CLI, qwatch telemetry disable writes a repo-local opt-out file and qwatch telemetry enable removes or neutralizes only qwatch-managed repo-local opt-out state. QueryWatch-owned files use managedBy: "qwatch" as the ownership marker. Higher-precedence process environment variables still win, and existing non-qwatch-managed repo-local config is left untouched.

License

MIT

About

QueryWatch – lightweight .NET library and CLI to catch N+1 queries and slow SQL in your tests; enforce query count and timing budgets, export JSON, and plug directly into CI/CD to fail builds before regressions reach production.

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

97 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

QueryWatch

QueryWatch is a .NET library for catching database-query regressions in tests and CI before they reach production. It records executed SQL, counts queries, measures timings, exports JSON summaries, and lets you fail builds on budget violations.

Works with:

  • ADO.NET
  • Dapper
  • EF Core

Why Use It

QueryWatch is designed for test-time guardrails, not production profiling dashboards.

Typical use cases:

  • Catch N+1 regressions introduced by ORM changes
  • Enforce per-test query-count budgets
  • Fail CI when average or total SQL time drifts upward
  • Export machine-readable summaries for baselines and PR reporting
  • Capture parameter shape metadata without storing parameter values

Packages

PackagePurpose
KeelMatrix.QueryWatchCore recording, assertions, JSON export, ADO.NET and Dapper wrapping
KeelMatrix.QueryWatch.EfCoreEF Core interceptor and UseQueryWatch(...) integration
qwatch.NET tool for enforcing query and SQL performance budgets in CI

Install

Core only:

dotnet add package KeelMatrix.QueryWatch

EF Core integration:

dotnet add package KeelMatrix.QueryWatch
dotnet add package KeelMatrix.QueryWatch.EfCore

Optional redaction helpers:

dotnet add package KeelMatrix.Redaction

QueryWatch restores KeelMatrix.Redaction and KeelMatrix.Telemetry 0.1.0 from NuGet.org. The sample helper uses a local feed only for QueryWatch packages built from this repository.

Install the public CI tool:

dotnet tool install --global qwatch --version 0.1.0

5-Minute Quick Start

usingKeelMatrix.QueryWatch;usingKeelMatrix.QueryWatch.Assertions;usingKeelMatrix.QueryWatch.Reporting;usingQueryWatchSessionsession=new();usingvarconn=rawConnection.WithQueryWatch(session);// Run code that talks to the database.QueryWatchReportreport=session.Complete();report.ShouldHaveExecutedAtMost(20);report.ShouldHaveMaxAverageTime(TimeSpan.FromMilliseconds(15));QueryWatchJson.ExportToFile(report,"artifacts/qwatch.report.json",sampleTop:200);

The exported JSON can be consumed by the CLI in CI.

Real-World Scenarios

Prevent accidental N+1 queries

Wrap the test scope, execute the application code, and assert the query count stays below a fixed threshold.

Gate pull requests on SQL budgets

Export a summary file during tests, then run the CLI in GitHub Actions to fail the build if query counts or timings regress.

Track parameter shape safely

Enable parameter-shape capture to understand whether code is issuing parameterized commands, without persisting sensitive parameter values.

Normalize SQL before comparisons

Add redactors to remove secrets, GUID noise, timestamps, or tokens so CI diffs focus on structural query changes.

Quick Start - Samples (Local)

This repo ships three sample apps that consume local packages built from source. The helper scripts pack QueryWatch core and EF Core locally; Redaction and Telemetry restore from NuGet.org.

  1. Build and pack the local packages used by the samples:

    • PowerShell: pwsh -NoProfile -File build/Dev-PackInstallSamples.ps1
    • bash: bash build/Dev-PackInstallSamples.sh
  2. Run a sample:

    dotnet run --project ./samples/EFCore.Sqlite/EFCore.Sqlite.csproj -c Release
  3. Gate the generated summary with the CLI:

    dotnet run --project ./tools/KeelMatrix.QueryWatch.Cli -- --input ./samples/EFCore.Sqlite/bin/Release/net8.0/artifacts/qwatch.ef.json --max-queries 50

EF Core Wiring

usingKeelMatrix.QueryWatch;usingKeelMatrix.QueryWatch.EfCore;usingKeelMatrix.QueryWatch.Reporting;usingvarsession=newQueryWatchSession();varoptions=newDbContextOptionsBuilder<MyDbContext>().UseSqlite("Data Source=:memory:").UseQueryWatch(session).Options;// Run workload...varreport=session.Complete();QueryWatchJson.ExportToFile(report,"artifacts/ef.json",sampleTop:200);

The EF Core interceptor records executed commands only. Use QueryWatchOptions to tune SQL text capture, sampling, and parameter-shape capture.

Dapper Wiring

usingKeelMatrix.QueryWatch;usingKeelMatrix.QueryWatch.Reporting;usingvarsession=newQueryWatchSession();awaitusingvarraw=newSqliteConnection("Data Source=:memory:");awaitraw.OpenAsync();usingvarconn=raw.WithQueryWatch(session);varrows=awaitconn.QueryAsync("SELECT 1");varreport=session.Complete();QueryWatchJson.ExportToFile(report,"artifacts/dapper.json",sampleTop:200);

If the underlying connection is a DbConnection, QueryWatch uses the higher-fidelity ADO.NET wrapper automatically.

ADO.NET Wiring

usingKeelMatrix.QueryWatch;usingKeelMatrix.QueryWatch.Reporting;usingvarsession=newQueryWatchSession();awaitusingvarraw=newSqliteConnection("Data Source=:memory:");awaitraw.OpenAsync();usingvarconn=raw.WithQueryWatch(session);usingvarcmd=conn.CreateCommand();cmd.CommandText="SELECT 1";awaitcmd.ExecuteNonQueryAsync();varreport=session.Complete();QueryWatchJson.ExportToFile(report,"artifacts/ado.json",sampleTop:200);

Redaction

If captured SQL can include secrets, tokens, email addresses, or noisy identifiers, add KeelMatrix.Redaction and configure redactors on QueryWatchOptions.

usingKeelMatrix.QueryWatch;usingKeelMatrix.QueryWatch.Redaction;usingKeelMatrix.Redaction;varoptions=newQueryWatchOptions().UseRecommendedRedactors(includeTimestamps:false,includeIpAddresses:false,includePhone:false);

Budgets

At test time, enforce budgets directly on QueryWatchReport.

At CI time, use the CLI for:

  • total-query budgets
  • average-duration budgets
  • total-duration budgets
  • baseline comparisons
  • per-pattern budgets

Per-pattern budgets support wildcards (*, ?) or a regex: prefix.

Examples:

--budget "SELECT * FROM Users*=1"
--budget "regex:^UPDATE Orders SET=3"

If a summary is top-N sampled, budgets are evaluated only over those captured events. Increase sampleTop if you need stricter guarantees.

CLI

Usage:
qwatch --input file.json [options]
qwatch telemetry <status|disable|enable> [options]
Commands:
telemetry status [--json] Show effective telemetry state and repo-local config status for the current repo.
telemetry disable Write a qwatch-managed repo-local telemetry opt-out.
telemetry enable Remove or neutralize qwatch-managed repo-local telemetry opt-out.
Options:
--input <path> Input JSON summary file. (repeatable)
--max-queries N Fail if total query count exceeds N.
--max-average-ms N Fail if average duration exceeds N ms.
--max-total-ms N Fail if total duration exceeds N ms.
--baseline <path> Baseline summary JSON to compare against.
--baseline-allow-percent P Allow +P% regression vs baseline before failing.
--write-baseline Write current aggregated summary to --baseline.
--budget "<pattern>=<max>" Per-pattern query count budget. (repeatable)
Pattern supports wildcards (*, ?) or prefix with 'regex:' for raw regex.
--require-full-events Fail if input summaries are top-N sampled.
--help Show this help.

Multi-file support:

  • repeat --input to aggregate summaries from multiple test projects
  • compare current results against a baseline summary
  • write GitHub Actions step summaries automatically when running in CI
  • inspect or manage repo-local telemetry opt-out state with qwatch telemetry status|disable|enable

Troubleshooting

  • Pattern budgets look incomplete: your summary may be sampled too aggressively. Re-export with a higher sampleTop.
  • Baseline checks are noisy: use --baseline-allow-percent and keep baselines representative.
  • CLI flags in the README look stale: refresh the generated block with build/Update-ReadmeFlags.ps1.
  • You do not want SQL text on a hot path: set QueryWatchOptions.CaptureSqlText = false.
  • You want metadata without secret leakage: use parameter-shape capture, not parameter-value capture.

Privacy

QueryWatch uses KeelMatrix.Telemetry transitively for minimal anonymous usage telemetry.

See:

For the CLI, qwatch telemetry disable writes a repo-local opt-out file and qwatch telemetry enable removes or neutralizes only qwatch-managed repo-local opt-out state. QueryWatch-owned files use managedBy: "qwatch" as the ownership marker. Higher-precedence process environment variables still win, and existing non-qwatch-managed repo-local config is left untouched.

License

MIT

About

QueryWatch – lightweight .NET library and CLI to catch N+1 queries and slow SQL in your tests; enforce query count and timing budgets, export JSON, and plug directly into CI/CD to fail builds before regressions reach production.

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length \u003e 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

97 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

QueryWatch

QueryWatch is a .NET library for catching database-query regressions in tests and CI before they reach production. It records executed SQL, counts queries, measures timings, exports JSON summaries, and lets you fail builds on budget violations.

Works with:

  • ADO.NET
  • Dapper
  • EF Core

Why Use It

QueryWatch is designed for test-time guardrails, not production profiling dashboards.

Typical use cases:

  • Catch N+1 regressions introduced by ORM changes
  • Enforce per-test query-count budgets
  • Fail CI when average or total SQL time drifts upward
  • Export machine-readable summaries for baselines and PR reporting
  • Capture parameter shape metadata without storing parameter values

Packages

PackagePurpose
KeelMatrix.QueryWatchCore recording, assertions, JSON export, ADO.NET and Dapper wrapping
KeelMatrix.QueryWatch.EfCoreEF Core interceptor and UseQueryWatch(...) integration
qwatch.NET tool for enforcing query and SQL performance budgets in CI

Install

Core only:

dotnet add package KeelMatrix.QueryWatch

EF Core integration:

dotnet add package KeelMatrix.QueryWatch
dotnet add package KeelMatrix.QueryWatch.EfCore

Optional redaction helpers:

dotnet add package KeelMatrix.Redaction

QueryWatch restores KeelMatrix.Redaction and KeelMatrix.Telemetry 0.1.0 from NuGet.org. The sample helper uses a local feed only for QueryWatch packages built from this repository.

Install the public CI tool:

dotnet tool install --global qwatch --version 0.1.0

5-Minute Quick Start

usingKeelMatrix.QueryWatch;usingKeelMatrix.QueryWatch.Assertions;usingKeelMatrix.QueryWatch.Reporting;usingQueryWatchSessionsession=new();usingvarconn=rawConnection.WithQueryWatch(session);// Run code that talks to the database.QueryWatchReportreport=session.Complete();report.ShouldHaveExecutedAtMost(20);report.ShouldHaveMaxAverageTime(TimeSpan.FromMilliseconds(15));QueryWatchJson.ExportToFile(report,"artifacts/qwatch.report.json",sampleTop:200);

The exported JSON can be consumed by the CLI in CI.

Real-World Scenarios

Prevent accidental N+1 queries

Wrap the test scope, execute the application code, and assert the query count stays below a fixed threshold.

Gate pull requests on SQL budgets

Export a summary file during tests, then run the CLI in GitHub Actions to fail the build if query counts or timings regress.

Track parameter shape safely

Enable parameter-shape capture to understand whether code is issuing parameterized commands, without persisting sensitive parameter values.

Normalize SQL before comparisons

Add redactors to remove secrets, GUID noise, timestamps, or tokens so CI diffs focus on structural query changes.

Quick Start - Samples (Local)

This repo ships three sample apps that consume local packages built from source. The helper scripts pack QueryWatch core and EF Core locally; Redaction and Telemetry restore from NuGet.org.

  1. Build and pack the local packages used by the samples:

    • PowerShell: pwsh -NoProfile -File build/Dev-PackInstallSamples.ps1
    • bash: bash build/Dev-PackInstallSamples.sh
  2. Run a sample:

    dotnet run --project ./samples/EFCore.Sqlite/EFCore.Sqlite.csproj -c Release
  3. Gate the generated summary with the CLI:

    dotnet run --project ./tools/KeelMatrix.QueryWatch.Cli -- --input ./samples/EFCore.Sqlite/bin/Release/net8.0/artifacts/qwatch.ef.json --max-queries 50

EF Core Wiring

usingKeelMatrix.QueryWatch;usingKeelMatrix.QueryWatch.EfCore;usingKeelMatrix.QueryWatch.Reporting;usingvarsession=newQueryWatchSession();varoptions=newDbContextOptionsBuilder<MyDbContext>().UseSqlite("Data Source=:memory:").UseQueryWatch(session).Options;// Run workload...varreport=session.Complete();QueryWatchJson.ExportToFile(report,"artifacts/ef.json",sampleTop:200);

The EF Core interceptor records executed commands only. Use QueryWatchOptions to tune SQL text capture, sampling, and parameter-shape capture.

Dapper Wiring

usingKeelMatrix.QueryWatch;usingKeelMatrix.QueryWatch.Reporting;usingvarsession=newQueryWatchSession();awaitusingvarraw=newSqliteConnection("Data Source=:memory:");awaitraw.OpenAsync();usingvarconn=raw.WithQueryWatch(session);varrows=awaitconn.QueryAsync("SELECT 1");varreport=session.Complete();QueryWatchJson.ExportToFile(report,"artifacts/dapper.json",sampleTop:200);

If the underlying connection is a DbConnection, QueryWatch uses the higher-fidelity ADO.NET wrapper automatically.

ADO.NET Wiring

usingKeelMatrix.QueryWatch;usingKeelMatrix.QueryWatch.Reporting;usingvarsession=newQueryWatchSession();awaitusingvarraw=newSqliteConnection("Data Source=:memory:");awaitraw.OpenAsync();usingvarconn=raw.WithQueryWatch(session);usingvarcmd=conn.CreateCommand();cmd.CommandText="SELECT 1";awaitcmd.ExecuteNonQueryAsync();varreport=session.Complete();QueryWatchJson.ExportToFile(report,"artifacts/ado.json",sampleTop:200);

Redaction

If captured SQL can include secrets, tokens, email addresses, or noisy identifiers, add KeelMatrix.Redaction and configure redactors on QueryWatchOptions.

usingKeelMatrix.QueryWatch;usingKeelMatrix.QueryWatch.Redaction;usingKeelMatrix.Redaction;varoptions=newQueryWatchOptions().UseRecommendedRedactors(includeTimestamps:false,includeIpAddresses:false,includePhone:false);

Budgets

At test time, enforce budgets directly on QueryWatchReport.

At CI time, use the CLI for:

  • total-query budgets
  • average-duration budgets
  • total-duration budgets
  • baseline comparisons
  • per-pattern budgets

Per-pattern budgets support wildcards (*, ?) or a regex: prefix.

Examples:

--budget "SELECT * FROM Users*=1"
--budget "regex:^UPDATE Orders SET=3"

If a summary is top-N sampled, budgets are evaluated only over those captured events. Increase sampleTop if you need stricter guarantees.

CLI

Usage:
qwatch --input file.json [options]
qwatch telemetry <status|disable|enable> [options]
Commands:
telemetry status [--json] Show effective telemetry state and repo-local config status for the current repo.
telemetry disable Write a qwatch-managed repo-local telemetry opt-out.
telemetry enable Remove or neutralize qwatch-managed repo-local telemetry opt-out.
Options:
--input <path> Input JSON summary file. (repeatable)
--max-queries N Fail if total query count exceeds N.
--max-average-ms N Fail if average duration exceeds N ms.
--max-total-ms N Fail if total duration exceeds N ms.
--baseline <path> Baseline summary JSON to compare against.
--baseline-allow-percent P Allow +P% regression vs baseline before failing.
--write-baseline Write current aggregated summary to --baseline.
--budget "<pattern>=<max>" Per-pattern query count budget. (repeatable)
Pattern supports wildcards (*, ?) or prefix with 'regex:' for raw regex.
--require-full-events Fail if input summaries are top-N sampled.
--help Show this help.

Multi-file support:

  • repeat --input to aggregate summaries from multiple test projects
  • compare current results against a baseline summary
  • write GitHub Actions step summaries automatically when running in CI
  • inspect or manage repo-local telemetry opt-out state with qwatch telemetry status|disable|enable

Troubleshooting

  • Pattern budgets look incomplete: your summary may be sampled too aggressively. Re-export with a higher sampleTop.
  • Baseline checks are noisy: use --baseline-allow-percent and keep baselines representative.
  • CLI flags in the README look stale: refresh the generated block with build/Update-ReadmeFlags.ps1.
  • You do not want SQL text on a hot path: set QueryWatchOptions.CaptureSqlText = false.
  • You want metadata without secret leakage: use parameter-shape capture, not parameter-value capture.

Privacy

QueryWatch uses KeelMatrix.Telemetry transitively for minimal anonymous usage telemetry.

See:

For the CLI, qwatch telemetry disable writes a repo-local opt-out file and qwatch telemetry enable removes or neutralizes only qwatch-managed repo-local opt-out state. QueryWatch-owned files use managedBy: "qwatch" as the ownership marker. Higher-precedence process environment variables still win, and existing non-qwatch-managed repo-local config is left untouched.

License

MIT

About

QueryWatch – lightweight .NET library and CLI to catch N+1 queries and slow SQL in your tests; enforce query count and timing budgets, export JSON, and plug directly into CI/CD to fail builds before regressions reach production.

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Latest commit

History

97 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

QueryWatch

QueryWatch is a .NET library for catching database-query regressions in tests and CI before they reach production. It records executed SQL, counts queries, measures timings, exports JSON summaries, and lets you fail builds on budget violations.

Works with:

  • ADO.NET
  • Dapper
  • EF Core

Why Use It

QueryWatch is designed for test-time guardrails, not production profiling dashboards.

Typical use cases:

  • Catch N+1 regressions introduced by ORM changes
  • Enforce per-test query-count budgets
  • Fail CI when average or total SQL time drifts upward
  • Export machine-readable summaries for baselines and PR reporting
  • Capture parameter shape metadata without storing parameter values

Packages

PackagePurpose
KeelMatrix.QueryWatchCore recording, assertions, JSON export, ADO.NET and Dapper wrapping
KeelMatrix.QueryWatch.EfCoreEF Core interceptor and UseQueryWatch(...) integration
qwatch.NET tool for enforcing query and SQL performance budgets in CI

Install

Core only:

dotnet add package KeelMatrix.QueryWatch

EF Core integration:

dotnet add package KeelMatrix.QueryWatch
dotnet add package KeelMatrix.QueryWatch.EfCore

Optional redaction helpers:

dotnet add package KeelMatrix.Redaction

QueryWatch restores KeelMatrix.Redaction and KeelMatrix.Telemetry 0.1.0 from NuGet.org. The sample helper uses a local feed only for QueryWatch packages built from this repository.

Install the public CI tool:

dotnet tool install --global qwatch --version 0.1.0

5-Minute Quick Start

usingKeelMatrix.QueryWatch;usingKeelMatrix.QueryWatch.Assertions;usingKeelMatrix.QueryWatch.Reporting;usingQueryWatchSessionsession=new();usingvarconn=rawConnection.WithQueryWatch(session);// Run code that talks to the database.QueryWatchReportreport=session.Complete();report.ShouldHaveExecutedAtMost(20);report.ShouldHaveMaxAverageTime(TimeSpan.FromMilliseconds(15));QueryWatchJson.ExportToFile(report,"artifacts/qwatch.report.json",sampleTop:200);

The exported JSON can be consumed by the CLI in CI.

Real-World Scenarios

Prevent accidental N+1 queries

Wrap the test scope, execute the application code, and assert the query count stays below a fixed threshold.

Gate pull requests on SQL budgets

Export a summary file during tests, then run the CLI in GitHub Actions to fail the build if query counts or timings regress.

Track parameter shape safely

Enable parameter-shape capture to understand whether code is issuing parameterized commands, without persisting sensitive parameter values.

Normalize SQL before comparisons

Add redactors to remove secrets, GUID noise, timestamps, or tokens so CI diffs focus on structural query changes.

Quick Start - Samples (Local)

This repo ships three sample apps that consume local packages built from source. The helper scripts pack QueryWatch core and EF Core locally; Redaction and Telemetry restore from NuGet.org.

  1. Build and pack the local packages used by the samples:

    • PowerShell: pwsh -NoProfile -File build/Dev-PackInstallSamples.ps1
    • bash: bash build/Dev-PackInstallSamples.sh
  2. Run a sample:

    dotnet run --project ./samples/EFCore.Sqlite/EFCore.Sqlite.csproj -c Release
  3. Gate the generated summary with the CLI:

    dotnet run --project ./tools/KeelMatrix.QueryWatch.Cli -- --input ./samples/EFCore.Sqlite/bin/Release/net8.0/artifacts/qwatch.ef.json --max-queries 50

EF Core Wiring

usingKeelMatrix.QueryWatch;usingKeelMatrix.QueryWatch.EfCore;usingKeelMatrix.QueryWatch.Reporting;usingvarsession=newQueryWatchSession();varoptions=newDbContextOptionsBuilder<MyDbContext>().UseSqlite("Data Source=:memory:").UseQueryWatch(session).Options;// Run workload...varreport=session.Complete();QueryWatchJson.ExportToFile(report,"artifacts/ef.json",sampleTop:200);

The EF Core interceptor records executed commands only. Use QueryWatchOptions to tune SQL text capture, sampling, and parameter-shape capture.

Dapper Wiring

usingKeelMatrix.QueryWatch;usingKeelMatrix.QueryWatch.Reporting;usingvarsession=newQueryWatchSession();awaitusingvarraw=newSqliteConnection("Data Source=:memory:");awaitraw.OpenAsync();usingvarconn=raw.WithQueryWatch(session);varrows=awaitconn.QueryAsync("SELECT 1");varreport=session.Complete();QueryWatchJson.ExportToFile(report,"artifacts/dapper.json",sampleTop:200);

If the underlying connection is a DbConnection, QueryWatch uses the higher-fidelity ADO.NET wrapper automatically.

ADO.NET Wiring

usingKeelMatrix.QueryWatch;usingKeelMatrix.QueryWatch.Reporting;usingvarsession=newQueryWatchSession();awaitusingvarraw=newSqliteConnection("Data Source=:memory:");awaitraw.OpenAsync();usingvarconn=raw.WithQueryWatch(session);usingvarcmd=conn.CreateCommand();cmd.CommandText="SELECT 1";awaitcmd.ExecuteNonQueryAsync();varreport=session.Complete();QueryWatchJson.ExportToFile(report,"artifacts/ado.json",sampleTop:200);

Redaction

If captured SQL can include secrets, tokens, email addresses, or noisy identifiers, add KeelMatrix.Redaction and configure redactors on QueryWatchOptions.

usingKeelMatrix.QueryWatch;usingKeelMatrix.QueryWatch.Redaction;usingKeelMatrix.Redaction;varoptions=newQueryWatchOptions().UseRecommendedRedactors(includeTimestamps:false,includeIpAddresses:false,includePhone:false);

Budgets

At test time, enforce budgets directly on QueryWatchReport.

At CI time, use the CLI for:

  • total-query budgets
  • average-duration budgets
  • total-duration budgets
  • baseline comparisons
  • per-pattern budgets

Per-pattern budgets support wildcards (*, ?) or a regex: prefix.

Examples:

--budget "SELECT * FROM Users*=1"
--budget "regex:^UPDATE Orders SET=3"

If a summary is top-N sampled, budgets are evaluated only over those captured events. Increase sampleTop if you need stricter guarantees.

CLI

Usage:
qwatch --input file.json [options]
qwatch telemetry <status|disable|enable> [options]
Commands:
telemetry status [--json] Show effective telemetry state and repo-local config status for the current repo.
telemetry disable Write a qwatch-managed repo-local telemetry opt-out.
telemetry enable Remove or neutralize qwatch-managed repo-local telemetry opt-out.
Options:
--input <path> Input JSON summary file. (repeatable)
--max-queries N Fail if total query count exceeds N.
--max-average-ms N Fail if average duration exceeds N ms.
--max-total-ms N Fail if total duration exceeds N ms.
--baseline <path> Baseline summary JSON to compare against.
--baseline-allow-percent P Allow +P% regression vs baseline before failing.
--write-baseline Write current aggregated summary to --baseline.
--budget "<pattern>=<max>" Per-pattern query count budget. (repeatable)
Pattern supports wildcards (*, ?) or prefix with 'regex:' for raw regex.
--require-full-events Fail if input summaries are top-N sampled.
--help Show this help.

Multi-file support:

  • repeat --input to aggregate summaries from multiple test projects
  • compare current results against a baseline summary
  • write GitHub Actions step summaries automatically when running in CI
  • inspect or manage repo-local telemetry opt-out state with qwatch telemetry status|disable|enable

Troubleshooting

  • Pattern budgets look incomplete: your summary may be sampled too aggressively. Re-export with a higher sampleTop.
  • Baseline checks are noisy: use --baseline-allow-percent and keep baselines representative.
  • CLI flags in the README look stale: refresh the generated block with build/Update-ReadmeFlags.ps1.
  • You do not want SQL text on a hot path: set QueryWatchOptions.CaptureSqlText = false.
  • You want metadata without secret leakage: use parameter-shape capture, not parameter-value capture.

Privacy

QueryWatch uses KeelMatrix.Telemetry transitively for minimal anonymous usage telemetry.

See:

For the CLI, qwatch telemetry disable writes a repo-local opt-out file and qwatch telemetry enable removes or neutralizes only qwatch-managed repo-local opt-out state. QueryWatch-owned files use managedBy: "qwatch" as the ownership marker. Higher-precedence process environment variables still win, and existing non-qwatch-managed repo-local config is left untouched.

License

MIT

About

QueryWatch – lightweight .NET library and CLI to catch N+1 queries and slow SQL in your tests; enforce query count and timing budgets, export JSON, and plug directly into CI/CD to fail builds before regressions reach production.

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

97 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

QueryWatch

QueryWatch is a .NET library for catching database-query regressions in tests and CI before they reach production. It records executed SQL, counts queries, measures timings, exports JSON summaries, and lets you fail builds on budget violations.

Works with:

  • ADO.NET
  • Dapper
  • EF Core

Why Use It

QueryWatch is designed for test-time guardrails, not production profiling dashboards.

Typical use cases:

  • Catch N+1 regressions introduced by ORM changes
  • Enforce per-test query-count budgets
  • Fail CI when average or total SQL time drifts upward
  • Export machine-readable summaries for baselines and PR reporting
  • Capture parameter shape metadata without storing parameter values

Packages

PackagePurpose
KeelMatrix.QueryWatchCore recording, assertions, JSON export, ADO.NET and Dapper wrapping
KeelMatrix.QueryWatch.EfCoreEF Core interceptor and UseQueryWatch(...) integration
qwatch.NET tool for enforcing query and SQL performance budgets in CI

Install

Core only:

dotnet add package KeelMatrix.QueryWatch

EF Core integration:

dotnet add package KeelMatrix.QueryWatch
dotnet add package KeelMatrix.QueryWatch.EfCore

Optional redaction helpers:

dotnet add package KeelMatrix.Redaction

QueryWatch restores KeelMatrix.Redaction and KeelMatrix.Telemetry 0.1.0 from NuGet.org. The sample helper uses a local feed only for QueryWatch packages built from this repository.

Install the public CI tool:

dotnet tool install --global qwatch --version 0.1.0

5-Minute Quick Start

usingKeelMatrix.QueryWatch;usingKeelMatrix.QueryWatch.Assertions;usingKeelMatrix.QueryWatch.Reporting;usingQueryWatchSessionsession=new();usingvarconn=rawConnection.WithQueryWatch(session);// Run code that talks to the database.QueryWatchReportreport=session.Complete();report.ShouldHaveExecutedAtMost(20);report.ShouldHaveMaxAverageTime(TimeSpan.FromMilliseconds(15));QueryWatchJson.ExportToFile(report,"artifacts/qwatch.report.json",sampleTop:200);

The exported JSON can be consumed by the CLI in CI.

Real-World Scenarios

Prevent accidental N+1 queries

Wrap the test scope, execute the application code, and assert the query count stays below a fixed threshold.

Gate pull requests on SQL budgets

Export a summary file during tests, then run the CLI in GitHub Actions to fail the build if query counts or timings regress.

Track parameter shape safely

Enable parameter-shape capture to understand whether code is issuing parameterized commands, without persisting sensitive parameter values.

Normalize SQL before comparisons

Add redactors to remove secrets, GUID noise, timestamps, or tokens so CI diffs focus on structural query changes.

Quick Start - Samples (Local)

This repo ships three sample apps that consume local packages built from source. The helper scripts pack QueryWatch core and EF Core locally; Redaction and Telemetry restore from NuGet.org.

  1. Build and pack the local packages used by the samples:

    • PowerShell: pwsh -NoProfile -File build/Dev-PackInstallSamples.ps1
    • bash: bash build/Dev-PackInstallSamples.sh
  2. Run a sample:

    dotnet run --project ./samples/EFCore.Sqlite/EFCore.Sqlite.csproj -c Release
  3. Gate the generated summary with the CLI:

    dotnet run --project ./tools/KeelMatrix.QueryWatch.Cli -- --input ./samples/EFCore.Sqlite/bin/Release/net8.0/artifacts/qwatch.ef.json --max-queries 50

EF Core Wiring

usingKeelMatrix.QueryWatch;usingKeelMatrix.QueryWatch.EfCore;usingKeelMatrix.QueryWatch.Reporting;usingvarsession=newQueryWatchSession();varoptions=newDbContextOptionsBuilder<MyDbContext>().UseSqlite("Data Source=:memory:").UseQueryWatch(session).Options;// Run workload...varreport=session.Complete();QueryWatchJson.ExportToFile(report,"artifacts/ef.json",sampleTop:200);

The EF Core interceptor records executed commands only. Use QueryWatchOptions to tune SQL text capture, sampling, and parameter-shape capture.

Dapper Wiring

usingKeelMatrix.QueryWatch;usingKeelMatrix.QueryWatch.Reporting;usingvarsession=newQueryWatchSession();awaitusingvarraw=newSqliteConnection("Data Source=:memory:");awaitraw.OpenAsync();usingvarconn=raw.WithQueryWatch(session);varrows=awaitconn.QueryAsync("SELECT 1");varreport=session.Complete();QueryWatchJson.ExportToFile(report,"artifacts/dapper.json",sampleTop:200);

If the underlying connection is a DbConnection, QueryWatch uses the higher-fidelity ADO.NET wrapper automatically.

ADO.NET Wiring

usingKeelMatrix.QueryWatch;usingKeelMatrix.QueryWatch.Reporting;usingvarsession=newQueryWatchSession();awaitusingvarraw=newSqliteConnection("Data Source=:memory:");awaitraw.OpenAsync();usingvarconn=raw.WithQueryWatch(session);usingvarcmd=conn.CreateCommand();cmd.CommandText="SELECT 1";awaitcmd.ExecuteNonQueryAsync();varreport=session.Complete();QueryWatchJson.ExportToFile(report,"artifacts/ado.json",sampleTop:200);

Redaction

If captured SQL can include secrets, tokens, email addresses, or noisy identifiers, add KeelMatrix.Redaction and configure redactors on QueryWatchOptions.

usingKeelMatrix.QueryWatch;usingKeelMatrix.QueryWatch.Redaction;usingKeelMatrix.Redaction;varoptions=newQueryWatchOptions().UseRecommendedRedactors(includeTimestamps:false,includeIpAddresses:false,includePhone:false);

Budgets

At test time, enforce budgets directly on QueryWatchReport.

At CI time, use the CLI for:

  • total-query budgets
  • average-duration budgets
  • total-duration budgets
  • baseline comparisons
  • per-pattern budgets

Per-pattern budgets support wildcards (*, ?) or a regex: prefix.

Examples:

--budget "SELECT * FROM Users*=1"
--budget "regex:^UPDATE Orders SET=3"

If a summary is top-N sampled, budgets are evaluated only over those captured events. Increase sampleTop if you need stricter guarantees.

CLI

Usage:
qwatch --input file.json [options]
qwatch telemetry <status|disable|enable> [options]
Commands:
telemetry status [--json] Show effective telemetry state and repo-local config status for the current repo.
telemetry disable Write a qwatch-managed repo-local telemetry opt-out.
telemetry enable Remove or neutralize qwatch-managed repo-local telemetry opt-out.
Options:
--input <path> Input JSON summary file. (repeatable)
--max-queries N Fail if total query count exceeds N.
--max-average-ms N Fail if average duration exceeds N ms.
--max-total-ms N Fail if total duration exceeds N ms.
--baseline <path> Baseline summary JSON to compare against.
--baseline-allow-percent P Allow +P% regression vs baseline before failing.
--write-baseline Write current aggregated summary to --baseline.
--budget "<pattern>=<max>" Per-pattern query count budget. (repeatable)
Pattern supports wildcards (*, ?) or prefix with 'regex:' for raw regex.
--require-full-events Fail if input summaries are top-N sampled.
--help Show this help.

Multi-file support:

  • repeat --input to aggregate summaries from multiple test projects
  • compare current results against a baseline summary
  • write GitHub Actions step summaries automatically when running in CI
  • inspect or manage repo-local telemetry opt-out state with qwatch telemetry status|disable|enable

Troubleshooting

  • Pattern budgets look incomplete: your summary may be sampled too aggressively. Re-export with a higher sampleTop.
  • Baseline checks are noisy: use --baseline-allow-percent and keep baselines representative.
  • CLI flags in the README look stale: refresh the generated block with build/Update-ReadmeFlags.ps1.
  • You do not want SQL text on a hot path: set QueryWatchOptions.CaptureSqlText = false.
  • You want metadata without secret leakage: use parameter-shape capture, not parameter-value capture.

Privacy

QueryWatch uses KeelMatrix.Telemetry transitively for minimal anonymous usage telemetry.

See:

For the CLI, qwatch telemetry disable writes a repo-local opt-out file and qwatch telemetry enable removes or neutralizes only qwatch-managed repo-local opt-out state. QueryWatch-owned files use managedBy: "qwatch" as the ownership marker. Higher-precedence process environment variables still win, and existing non-qwatch-managed repo-local config is left untouched.

License

MIT

About

QueryWatch – lightweight .NET library and CLI to catch N+1 queries and slow SQL in your tests; enforce query count and timing budgets, export JSON, and plug directly into CI/CD to fail builds before regressions reach production.

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

97 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

QueryWatch

QueryWatch is a .NET library for catching database-query regressions in tests and CI before they reach production. It records executed SQL, counts queries, measures timings, exports JSON summaries, and lets you fail builds on budget violations.

Works with:

  • ADO.NET
  • Dapper
  • EF Core

Why Use It

QueryWatch is designed for test-time guardrails, not production profiling dashboards.

Typical use cases:

  • Catch N+1 regressions introduced by ORM changes
  • Enforce per-test query-count budgets
  • Fail CI when average or total SQL time drifts upward
  • Export machine-readable summaries for baselines and PR reporting
  • Capture parameter shape metadata without storing parameter values

Packages

PackagePurpose
KeelMatrix.QueryWatchCore recording, assertions, JSON export, ADO.NET and Dapper wrapping
KeelMatrix.QueryWatch.EfCoreEF Core interceptor and UseQueryWatch(...) integration
qwatch.NET tool for enforcing query and SQL performance budgets in CI

Install

Core only:

dotnet add package KeelMatrix.QueryWatch

EF Core integration:

dotnet add package KeelMatrix.QueryWatch
dotnet add package KeelMatrix.QueryWatch.EfCore

Optional redaction helpers:

dotnet add package KeelMatrix.Redaction

QueryWatch restores KeelMatrix.Redaction and KeelMatrix.Telemetry 0.1.0 from NuGet.org. The sample helper uses a local feed only for QueryWatch packages built from this repository.

Install the public CI tool:

dotnet tool install --global qwatch --version 0.1.0

5-Minute Quick Start

usingKeelMatrix.QueryWatch;usingKeelMatrix.QueryWatch.Assertions;usingKeelMatrix.QueryWatch.Reporting;usingQueryWatchSessionsession=new();usingvarconn=rawConnection.WithQueryWatch(session);// Run code that talks to the database.QueryWatchReportreport=session.Complete();report.ShouldHaveExecutedAtMost(20);report.ShouldHaveMaxAverageTime(TimeSpan.FromMilliseconds(15));QueryWatchJson.ExportToFile(report,"artifacts/qwatch.report.json",sampleTop:200);

The exported JSON can be consumed by the CLI in CI.

Real-World Scenarios

Prevent accidental N+1 queries

Wrap the test scope, execute the application code, and assert the query count stays below a fixed threshold.

Gate pull requests on SQL budgets

Export a summary file during tests, then run the CLI in GitHub Actions to fail the build if query counts or timings regress.

Track parameter shape safely

Enable parameter-shape capture to understand whether code is issuing parameterized commands, without persisting sensitive parameter values.

Normalize SQL before comparisons

Add redactors to remove secrets, GUID noise, timestamps, or tokens so CI diffs focus on structural query changes.

Quick Start - Samples (Local)

This repo ships three sample apps that consume local packages built from source. The helper scripts pack QueryWatch core and EF Core locally; Redaction and Telemetry restore from NuGet.org.

  1. Build and pack the local packages used by the samples:

    • PowerShell: pwsh -NoProfile -File build/Dev-PackInstallSamples.ps1
    • bash: bash build/Dev-PackInstallSamples.sh
  2. Run a sample:

    dotnet run --project ./samples/EFCore.Sqlite/EFCore.Sqlite.csproj -c Release
  3. Gate the generated summary with the CLI:

    dotnet run --project ./tools/KeelMatrix.QueryWatch.Cli -- --input ./samples/EFCore.Sqlite/bin/Release/net8.0/artifacts/qwatch.ef.json --max-queries 50

EF Core Wiring

usingKeelMatrix.QueryWatch;usingKeelMatrix.QueryWatch.EfCore;usingKeelMatrix.QueryWatch.Reporting;usingvarsession=newQueryWatchSession();varoptions=newDbContextOptionsBuilder<MyDbContext>().UseSqlite("Data Source=:memory:").UseQueryWatch(session).Options;// Run workload...varreport=session.Complete();QueryWatchJson.ExportToFile(report,"artifacts/ef.json",sampleTop:200);

The EF Core interceptor records executed commands only. Use QueryWatchOptions to tune SQL text capture, sampling, and parameter-shape capture.

Dapper Wiring

usingKeelMatrix.QueryWatch;usingKeelMatrix.QueryWatch.Reporting;usingvarsession=newQueryWatchSession();awaitusingvarraw=newSqliteConnection("Data Source=:memory:");awaitraw.OpenAsync();usingvarconn=raw.WithQueryWatch(session);varrows=awaitconn.QueryAsync("SELECT 1");varreport=session.Complete();QueryWatchJson.ExportToFile(report,"artifacts/dapper.json",sampleTop:200);

If the underlying connection is a DbConnection, QueryWatch uses the higher-fidelity ADO.NET wrapper automatically.

ADO.NET Wiring

usingKeelMatrix.QueryWatch;usingKeelMatrix.QueryWatch.Reporting;usingvarsession=newQueryWatchSession();awaitusingvarraw=newSqliteConnection("Data Source=:memory:");awaitraw.OpenAsync();usingvarconn=raw.WithQueryWatch(session);usingvarcmd=conn.CreateCommand();cmd.CommandText="SELECT 1";awaitcmd.ExecuteNonQueryAsync();varreport=session.Complete();QueryWatchJson.ExportToFile(report,"artifacts/ado.json",sampleTop:200);

Redaction

If captured SQL can include secrets, tokens, email addresses, or noisy identifiers, add KeelMatrix.Redaction and configure redactors on QueryWatchOptions.

usingKeelMatrix.QueryWatch;usingKeelMatrix.QueryWatch.Redaction;usingKeelMatrix.Redaction;varoptions=newQueryWatchOptions().UseRecommendedRedactors(includeTimestamps:false,includeIpAddresses:false,includePhone:false);

Budgets

At test time, enforce budgets directly on QueryWatchReport.

At CI time, use the CLI for:

  • total-query budgets
  • average-duration budgets
  • total-duration budgets
  • baseline comparisons
  • per-pattern budgets

Per-pattern budgets support wildcards (*, ?) or a regex: prefix.

Examples:

--budget "SELECT * FROM Users*=1"
--budget "regex:^UPDATE Orders SET=3"

If a summary is top-N sampled, budgets are evaluated only over those captured events. Increase sampleTop if you need stricter guarantees.

CLI

Usage:
qwatch --input file.json [options]
qwatch telemetry <status|disable|enable> [options]
Commands:
telemetry status [--json] Show effective telemetry state and repo-local config status for the current repo.
telemetry disable Write a qwatch-managed repo-local telemetry opt-out.
telemetry enable Remove or neutralize qwatch-managed repo-local telemetry opt-out.
Options:
--input <path> Input JSON summary file. (repeatable)
--max-queries N Fail if total query count exceeds N.
--max-average-ms N Fail if average duration exceeds N ms.
--max-total-ms N Fail if total duration exceeds N ms.
--baseline <path> Baseline summary JSON to compare against.
--baseline-allow-percent P Allow +P% regression vs baseline before failing.
--write-baseline Write current aggregated summary to --baseline.
--budget "<pattern>=<max>" Per-pattern query count budget. (repeatable)
Pattern supports wildcards (*, ?) or prefix with 'regex:' for raw regex.
--require-full-events Fail if input summaries are top-N sampled.
--help Show this help.

Multi-file support:

  • repeat --input to aggregate summaries from multiple test projects
  • compare current results against a baseline summary
  • write GitHub Actions step summaries automatically when running in CI
  • inspect or manage repo-local telemetry opt-out state with qwatch telemetry status|disable|enable

Troubleshooting

  • Pattern budgets look incomplete: your summary may be sampled too aggressively. Re-export with a higher sampleTop.
  • Baseline checks are noisy: use --baseline-allow-percent and keep baselines representative.
  • CLI flags in the README look stale: refresh the generated block with build/Update-ReadmeFlags.ps1.
  • You do not want SQL text on a hot path: set QueryWatchOptions.CaptureSqlText = false.
  • You want metadata without secret leakage: use parameter-shape capture, not parameter-value capture.

Privacy

QueryWatch uses KeelMatrix.Telemetry transitively for minimal anonymous usage telemetry.

See:

For the CLI, qwatch telemetry disable writes a repo-local opt-out file and qwatch telemetry enable removes or neutralizes only qwatch-managed repo-local opt-out state. QueryWatch-owned files use managedBy: "qwatch" as the ownership marker. Higher-precedence process environment variables still win, and existing non-qwatch-managed repo-local config is left untouched.

License

MIT

About

QueryWatch – lightweight .NET library and CLI to catch N+1 queries and slow SQL in your tests; enforce query count and timing budgets, export JSON, and plug directly into CI/CD to fail builds before regressions reach production.

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Latest commit

History

97 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

QueryWatch

QueryWatch is a .NET library for catching database-query regressions in tests and CI before they reach production. It records executed SQL, counts queries, measures timings, exports JSON summaries, and lets you fail builds on budget violations.

Works with:

  • ADO.NET
  • Dapper
  • EF Core

Why Use It

QueryWatch is designed for test-time guardrails, not production profiling dashboards.

Typical use cases:

  • Catch N+1 regressions introduced by ORM changes
  • Enforce per-test query-count budgets
  • Fail CI when average or total SQL time drifts upward
  • Export machine-readable summaries for baselines and PR reporting
  • Capture parameter shape metadata without storing parameter values

Packages

PackagePurpose
KeelMatrix.QueryWatchCore recording, assertions, JSON export, ADO.NET and Dapper wrapping
KeelMatrix.QueryWatch.EfCoreEF Core interceptor and UseQueryWatch(...) integration
qwatch.NET tool for enforcing query and SQL performance budgets in CI

Install

Core only:

dotnet add package KeelMatrix.QueryWatch

EF Core integration:

dotnet add package KeelMatrix.QueryWatch
dotnet add package KeelMatrix.QueryWatch.EfCore

Optional redaction helpers:

dotnet add package KeelMatrix.Redaction

QueryWatch restores KeelMatrix.Redaction and KeelMatrix.Telemetry 0.1.0 from NuGet.org. The sample helper uses a local feed only for QueryWatch packages built from this repository.

Install the public CI tool:

dotnet tool install --global qwatch --version 0.1.0

5-Minute Quick Start

usingKeelMatrix.QueryWatch;usingKeelMatrix.QueryWatch.Assertions;usingKeelMatrix.QueryWatch.Reporting;usingQueryWatchSessionsession=new();usingvarconn=rawConnection.WithQueryWatch(session);// Run code that talks to the database.QueryWatchReportreport=session.Complete();report.ShouldHaveExecutedAtMost(20);report.ShouldHaveMaxAverageTime(TimeSpan.FromMilliseconds(15));QueryWatchJson.ExportToFile(report,"artifacts/qwatch.report.json",sampleTop:200);

The exported JSON can be consumed by the CLI in CI.

Real-World Scenarios

Prevent accidental N+1 queries

Wrap the test scope, execute the application code, and assert the query count stays below a fixed threshold.

Gate pull requests on SQL budgets

Export a summary file during tests, then run the CLI in GitHub Actions to fail the build if query counts or timings regress.

Track parameter shape safely

Enable parameter-shape capture to understand whether code is issuing parameterized commands, without persisting sensitive parameter values.

Normalize SQL before comparisons

Add redactors to remove secrets, GUID noise, timestamps, or tokens so CI diffs focus on structural query changes.

Quick Start - Samples (Local)

This repo ships three sample apps that consume local packages built from source. The helper scripts pack QueryWatch core and EF Core locally; Redaction and Telemetry restore from NuGet.org.

  1. Build and pack the local packages used by the samples:

    • PowerShell: pwsh -NoProfile -File build/Dev-PackInstallSamples.ps1
    • bash: bash build/Dev-PackInstallSamples.sh
  2. Run a sample:

    dotnet run --project ./samples/EFCore.Sqlite/EFCore.Sqlite.csproj -c Release
  3. Gate the generated summary with the CLI:

    dotnet run --project ./tools/KeelMatrix.QueryWatch.Cli -- --input ./samples/EFCore.Sqlite/bin/Release/net8.0/artifacts/qwatch.ef.json --max-queries 50

EF Core Wiring

usingKeelMatrix.QueryWatch;usingKeelMatrix.QueryWatch.EfCore;usingKeelMatrix.QueryWatch.Reporting;usingvarsession=newQueryWatchSession();varoptions=newDbContextOptionsBuilder<MyDbContext>().UseSqlite("Data Source=:memory:").UseQueryWatch(session).Options;// Run workload...varreport=session.Complete();QueryWatchJson.ExportToFile(report,"artifacts/ef.json",sampleTop:200);

The EF Core interceptor records executed commands only. Use QueryWatchOptions to tune SQL text capture, sampling, and parameter-shape capture.

Dapper Wiring

usingKeelMatrix.QueryWatch;usingKeelMatrix.QueryWatch.Reporting;usingvarsession=newQueryWatchSession();awaitusingvarraw=newSqliteConnection("Data Source=:memory:");awaitraw.OpenAsync();usingvarconn=raw.WithQueryWatch(session);varrows=awaitconn.QueryAsync("SELECT 1");varreport=session.Complete();QueryWatchJson.ExportToFile(report,"artifacts/dapper.json",sampleTop:200);

If the underlying connection is a DbConnection, QueryWatch uses the higher-fidelity ADO.NET wrapper automatically.

ADO.NET Wiring

usingKeelMatrix.QueryWatch;usingKeelMatrix.QueryWatch.Reporting;usingvarsession=newQueryWatchSession();awaitusingvarraw=newSqliteConnection("Data Source=:memory:");awaitraw.OpenAsync();usingvarconn=raw.WithQueryWatch(session);usingvarcmd=conn.CreateCommand();cmd.CommandText="SELECT 1";awaitcmd.ExecuteNonQueryAsync();varreport=session.Complete();QueryWatchJson.ExportToFile(report,"artifacts/ado.json",sampleTop:200);

Redaction

If captured SQL can include secrets, tokens, email addresses, or noisy identifiers, add KeelMatrix.Redaction and configure redactors on QueryWatchOptions.

usingKeelMatrix.QueryWatch;usingKeelMatrix.QueryWatch.Redaction;usingKeelMatrix.Redaction;varoptions=newQueryWatchOptions().UseRecommendedRedactors(includeTimestamps:false,includeIpAddresses:false,includePhone:false);

Budgets

At test time, enforce budgets directly on QueryWatchReport.

At CI time, use the CLI for:

  • total-query budgets
  • average-duration budgets
  • total-duration budgets
  • baseline comparisons
  • per-pattern budgets

Per-pattern budgets support wildcards (*, ?) or a regex: prefix.

Examples:

--budget "SELECT * FROM Users*=1"
--budget "regex:^UPDATE Orders SET=3"

If a summary is top-N sampled, budgets are evaluated only over those captured events. Increase sampleTop if you need stricter guarantees.

CLI

Usage:
qwatch --input file.json [options]
qwatch telemetry <status|disable|enable> [options]
Commands:
telemetry status [--json] Show effective telemetry state and repo-local config status for the current repo.
telemetry disable Write a qwatch-managed repo-local telemetry opt-out.
telemetry enable Remove or neutralize qwatch-managed repo-local telemetry opt-out.
Options:
--input <path> Input JSON summary file. (repeatable)
--max-queries N Fail if total query count exceeds N.
--max-average-ms N Fail if average duration exceeds N ms.
--max-total-ms N Fail if total duration exceeds N ms.
--baseline <path> Baseline summary JSON to compare against.
--baseline-allow-percent P Allow +P% regression vs baseline before failing.
--write-baseline Write current aggregated summary to --baseline.
--budget "<pattern>=<max>" Per-pattern query count budget. (repeatable)
Pattern supports wildcards (*, ?) or prefix with 'regex:' for raw regex.
--require-full-events Fail if input summaries are top-N sampled.
--help Show this help.

Multi-file support:

  • repeat --input to aggregate summaries from multiple test projects
  • compare current results against a baseline summary
  • write GitHub Actions step summaries automatically when running in CI
  • inspect or manage repo-local telemetry opt-out state with qwatch telemetry status|disable|enable

Troubleshooting

  • Pattern budgets look incomplete: your summary may be sampled too aggressively. Re-export with a higher sampleTop.
  • Baseline checks are noisy: use --baseline-allow-percent and keep baselines representative.
  • CLI flags in the README look stale: refresh the generated block with build/Update-ReadmeFlags.ps1.
  • You do not want SQL text on a hot path: set QueryWatchOptions.CaptureSqlText = false.
  • You want metadata without secret leakage: use parameter-shape capture, not parameter-value capture.

Privacy

QueryWatch uses KeelMatrix.Telemetry transitively for minimal anonymous usage telemetry.

See:

For the CLI, qwatch telemetry disable writes a repo-local opt-out file and qwatch telemetry enable removes or neutralizes only qwatch-managed repo-local opt-out state. QueryWatch-owned files use managedBy: "qwatch" as the ownership marker. Higher-precedence process environment variables still win, and existing non-qwatch-managed repo-local config is left untouched.

License

MIT

About

QueryWatch – lightweight .NET library and CLI to catch N+1 queries and slow SQL in your tests; enforce query count and timing budgets, export JSON, and plug directly into CI/CD to fail builds before regressions reach production.

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages