Chapter 18 · Diagnostics, Logging, Interceptors, Metrics, and Observability
Query Tags, Command Timeouts, Slow-Query Correlation, and Mapping ORM Events to Database Plans
Correlate a ServiceHub query from TagWith/TagWithCallSite through command duration and timeout evidence to SQLite EXPLAIN QUERY PLAN and optional SQL Server/PostgreSQL plan tools, without confusing client/provider timing with database execution cost.
Learning outcomes
Use TagWith and TagWithCallSite as stable correlation aids and understand that tags become SQL comments rather than parameters.
Configure command timeout deliberately and explain the provider-specific meaning of timeout, especially Microsoft.Data.Sqlite lock/busy behavior.
Capture EF command duration/error evidence without assuming it is the same as database CPU, I/O, lock-wait, or full result-enumeration time.
Correlate tagged EF SQL to SQLite EXPLAIN QUERY PLAN and identify equivalent optional plan tools for SQL Server and PostgreSQL.
Diagnose a semantically correct but inefficient ServiceHub query using query shape, index evidence, plan output, and trace/log correlation.
Build a slow-query triage workflow that protects sensitive SQL/parameters and avoids arbitrary global timeout tuning.
1. A “slow EF query” is a hypothesis, not a diagnosis
When a dispatch endpoint is slow, the delay may come from EF translation, waiting for a pooled connection, a database lock, an inefficient access path, transferring too many rows/columns, materializing/tracking a graph, serialization, or downstream work. Chapter 17 measured those phases in a lab. Production diagnostics now need a stable way to join the application operation, EF command, and database plan without copying parameters into every metric.
Mandatory baseline: .NET runtime 10.0.11, SDK 10.0.400, Microsoft.EntityFrameworkCore / Design / Sqlite and dotnet-ef 10.0.11. SQLite is the free local database. EF Core 11 preview APIs are out of scope. OpenTelemetry core/hosting 1.18.0 may be used for an optional console telemetry path; OpenTelemetry.Instrumentation.EntityFrameworkCore 1.18.0-beta.1 remains prerelease/beta and is never required for the mandatory labs.
2. Query tags create a stable bridge from LINQ to SQL evidence
var query = db.WorkOrders .TagWith("servicehub.operation=dispatch.queue") .TagWithCallSite() .AsNoTracking() .Where(w => w.Priority == WorkOrderPriority.High) .OrderByDescending(w => EF.Property<DateTime>(w, "CreatedUtc")) .ThenBy(w => w.Id) .Take(50) .Select(w => new { w.Id, w.WorkOrderNumber, w.CustomerName, CreatedUtc = EF.Property<DateTime>(w, "CreatedUtc") });Console.WriteLine(query.ToQueryString());var rows = await query.ToListAsync(cancellationToken);
EF emits the tag as a SQL comment.
TagWithCallSite can include the source filename and
line number. Query tags are not parameterizable; keep them
static and low-risk. Do not put request IDs, customer values,
usernames, secrets, or work-order numbers into a tag.
-- servicehub.operation=dispatch.queue-- file: .../DispatchQueries.cs:42SELECT "w"."work_order_id", "w"."work_order_number", "w"."customer_name", "w"."created_utc"FROM "work_orders" AS "w"WHERE "w"."priority" = @__priority_0ORDER BY "w"."created_utc" DESC, "w"."work_order_id"LIMIT @__p_1
3. ToQueryString and command logs are translation evidence, not timing truth
ToQueryString() shows diagnostic SQL text before
execution. A Database.Command completion event adds
provider-side command duration. Neither proves the database's
CPU/I/O plan, and neither includes the entire request pipeline.
The OpenTelemetry EF instrumentation likewise documents that
command activity duration does not include full enumeration of
all query results.
| Evidence | Useful for | Missing |
|---|---|---|
ToQueryString |
translation shape, predicates, projection, joins, tags | execution, locks, server plan, result enumeration |
| EF command event/interceptor | command execution duration/failure on client/provider boundary | request work outside command; database internal cause |
| database plan | access path, joins/scans/seeks, estimates/actuals depending engine/tool | EF translation/materialization/network outside server |
| request Activity | end-to-end correlated application timing | database-internal explanation unless linked |
4. Command timeout is provider behavior, not a universal “slow query” limit
EF can set a relational command timeout, but what the underlying
provider does with that value matters. For
Microsoft.Data.Sqlite, CommandTimeout is
specifically used while waiting to obtain a lock; the provider
automatically retries busy/locked errors until the timeout. It
is not a reliable CPU-time cancellation mechanism for an
arbitrary long-running SQLite statement.
await using var db = factory.CreateDbContext();var oldTimeout = db.Database.GetCommandTimeout();try{ db.Database.SetCommandTimeout(2); await RunLockContentionProbeAsync(db, cancellationToken);}finally{ db.Database.SetCommandTimeout(oldTimeout);}
SQL Server and other providers define command timeout according to their ADO.NET driver semantics. Never copy “2 seconds” into production because it worked in this lab. A production timeout must consider request deadline, retry policy, expected workload, transaction scope, lock behavior, and provider-specific cancellation.
5. Correlate the tag to SQLite EXPLAIN QUERY PLAN
The mandatory free database is SQLite. Use the SQL shape from
ToQueryString, substitute safe representative
literals in a disposable database, and run
EXPLAIN QUERY PLAN. Do not execute a production SQL
text containing secrets copied from logs.
EXPLAIN QUERY PLANSELECT work_order_id, work_order_number, customer_name, created_utcFROM work_ordersWHERE priority = 2ORDER BY created_utc DESC, work_order_idLIMIT 50;
Before useful index:SCAN work_ordersUSE TEMP B-TREE FOR ORDER BYAfter workload-justified index (example only):SEARCH work_orders USING INDEX ...Your actual output depends on schema, SQLite version,statistics, predicates and index definitions.
Do not invent a plan. Capture the learner's actual output and preserve it next to the tagged query and version record.
6. Optional provider plan tools remain provider-specific
| Provider | Typical plan evidence | Important caveat |
|---|---|---|
| SQLite | EXPLAIN QUERY PLAN |
compact planner summary; not an EF timing measurement |
| SQL Server/Azure SQL | actual execution plan, Query Store, SET STATISTICS IO/TIME where appropriate | permissions and production overhead; plans differ by parameter/cardinality/index/statistics |
| PostgreSQL |
EXPLAIN (ANALYZE, BUFFERS) in a safe
environment
|
ANALYZE executes the query; writes need transaction/rollback safety |
| MySQL/MariaDB/Oracle | engine/provider plan tooling | syntax and metrics differ; consult exact engine/version docs |
The point is not to memorize four plan syntaxes. It is to preserve an evidence boundary: EF tells you what it asked the provider to execute; the database tells you how it chose to execute it.
7. Failure case: “fix” every slow command by raising the timeout
A dashboard shows rising timeout errors. An engineer changes the global timeout from 30 seconds to five minutes. The errors disappear, but requests now hold locks/connections for longer and users wait minutes. The root cause—a new unindexed predicate—remains.
Repair: identify the operation tag, inspect command error/duration distribution, correlate to plan/lock evidence, reproduce with realistic data, then change query/index/schema or workload design. Adjust timeout only when the business operation legitimately needs a different deadline.
8. Slow-query exemplars need a stable query ID, not raw parameters
For a dashboard, keep a bounded identifier such as
servicehub.operation=dispatch.queue. Store a small
number of sampled trace IDs as exemplars in a trace/log system
with access control. Do not use full SQL or parameter values as
metric dimensions. If SQL text is retained for diagnostics,
control access and consider normalization/redaction.
event=ef.command.slowoperation=dispatch.queueprovider=sqliteoutcome=successduration_ms=...trace_id=...command_id=...parameters_captured=falsesql_payload_captured=false
9. Mandatory lab: build the evidence chain
-
Tag the dispatch-queue query with a stable operation name and
optionally
TagWithCallSite. -
Capture
ToQueryStringand a safe command completion/failure event. -
Run
EXPLAIN QUERY PLANagainst the disposable SQLite database. - If the plan shows a scan/sort that the workload makes expensive, design a candidate index in the lab and recapture the plan.
- Record command duration distribution before/after; do not publish a percentage unless measured.
- Create a separate lock-contention probe to show what SQLite's command timeout actually governs.
- Verify tags/logs contain no customer/work-order payloads.
10. Production judgment
Query tags are correlation metadata, not security boundaries; timeouts are provider- and operation-specific deadlines, not tuning knobs; EF command timing is one segment of the timeline, not the database plan. Keep the operation identifier stable enough to join traces, logs and plan investigations. The final lesson finishes by converting these signals into a production dashboard and runbook with bounded cardinality and actionable alerts.
Check your understanding
- What does TagWith do to generated relational SQL?
- Why should a query tag not contain a TraceId or work-order number?
- What does Microsoft.Data.Sqlite CommandTimeout primarily govern?
- What does ToQueryString fail to tell you?
- Why is EXPLAIN ANALYZE more dangerous than a non-executing plan command?
- What should be the first response to rising timeouts?
Review the answers
1. It adds the supplied static tag as a SQL comment, making application-to-SQL correlation easier.
2. Tags become SQL literals/comments and high-cardinality or sensitive values create telemetry/privacy problems; use traces/log fields for unique IDs.
3. How long the provider retries/waits when the database is busy or locked, not a universal CPU-time limit for every query.
4. Runtime timing, locks, database execution plan, server I/O/CPU, network cost and result enumeration/materialization.
5. On engines such as PostgreSQL it actually executes the statement; writes need a safe environment/transaction strategy.
6. Correlate the affected operation to command, lock/plan/provider evidence and diagnose the cause before globally increasing the timeout.
Authoritative references
Observability and diagnostics are version-, provider-, and topology-sensitive. Re-check these primary sources before standardizing a production telemetry contract.
- Query Tags - EF Core — TagWith, TagWithCallSite and tag limitations
- Performance Diagnosis - EF Core — logs, correlation, query tags and database plan workflow
- RelationalDatabaseFacadeExtensions.SetCommandTimeout — EF relational command-timeout configuration
- Microsoft.Data.Sqlite database errors — busy/locked retries and SQLite command-timeout semantics
- SQLite EXPLAIN QUERY PLAN — SQLite-specific plan evidence
- Interceptors - EF Core — command interception and duration/failure observation