A subtle bug in an EF Core interceptor spent a year lurking undetected because developers tested only against SQL Server. The mistake? Appending metadata to SQL commands after they executed. It worked on one database but backfired on SQLite, where open readers block text changes—and it clutters every query with invisible comments. The real irony: EF Core already provides execution timing in the callback you need, so the entire workaround was unnecessary.
How the bug crept into production
Debugging tools often hook into Entity Framework Core’s interception pipeline to log SQL, measure performance, or inject telemetry. One popular dashboard recorded every outgoing query by attaching a DbCommandInterceptor and appending a unique comment to each command’s text. The plan seemed harmless: glue a GUID and timestamp onto the CommandText property, then parse it back later to calculate elapsed time.
public override InterceptionResult<DbDataReader> ReaderExecuting(
DbCommand command,
CommandEventData eventData,
InterceptionResult<DbDataReader> result)
{
var id = Guid.NewGuid();
command.CommandText += $" /* {id}:{DateTime.UtcNow.Ticks} */";
return result;
}The logic worked well enough on SQL Server, which happily executed the injected comments. Developers never noticed the pollution because their local tests never touched SQLite. But once a user ran the tool against SQLite, the dashboard threw an exception:
System.InvalidOperationException:
An open reader is associated with this command. Close it before changing the CommandText property.SQLite enforces immutability on live commands, so modifying CommandText after execution crashes the runtime. The tool that was supposed to help debug queries had become the source of the problem.
Built-in timing is already in the callback
The deeper mistake wasn’t just mutation—it was solving a problem EF Core already solved. The ReaderExecuted callback receives both the executed command and the duration measured by the framework. All the correlation logic was redundant.
public override DbDataReader ReaderExecuted(
DbCommand command,
CommandExecutedEventData eventData,
DbDataReader result)
{
Record(command.CommandText, eventData.Duration);
return result;
}eventData.Duration is a TimeSpan provided by EF Core, eliminating the need to parse timestamps or juggle GUIDs. The command text is intact, logs remain clean, and SQLite stops crashing.
How to safely carry state between callbacks
If you truly need to transmit data from ReaderExecuting to ReaderExecuted—for example, capturing parameter values before execution—the correct approach is to use the CommandId property on the event data. This GUID is consistent across both callbacks and can key a concurrent dictionary on your side.
private readonly ConcurrentDictionary<Guid, MyState> _inflight = new();
public override InterceptionResult<DbDataReader> ReaderExecuting(
DbCommand command,
CommandEventData eventData,
InterceptionResult<DbDataReader> result)
{
_inflight[eventData.CommandId] = CaptureWhateverYouNeed();
return result;
}
public override DbDataReader ReaderExecuted(
DbCommand command,
CommandExecutedEventData eventData,
DbDataReader result)
{
if (_inflight.TryRemove(eventData.CommandId, out var state))
{
// Use state + eventData.Duration here
}
return result;
}The command object is for reading, not mutation. Keep your state in your own collections and leave the SQL untouched.
Rebuilding the dashboard the right way
After removing the mutation, the author rebuilt the debugging dashboard around the minimal interceptor shown above. Installing the tool now takes two lines of code—then open /_debug to see requests, parameterized SQL, logs, exceptions, and an N+1 detection warning when repeated queries fire in a single request. The project is MIT-licensed and targets .NET 8, 9, and 10.
Key takeaway for interceptor developers
Before you reach for clever SQL hacks in an EF Core interceptor, stop and check the callback signatures. EF Core often exposes exactly what you need without touching the command text. A single-line logger is cleaner, safer, and free of hidden side effects that only surface on certain databases. Audit your interceptors today and ensure they’re not the reason your next debugging session crashes instead of clarifies.
AI summary
EF Core interceptor’larında CommandText’i değiştirmek, SQLite çökmelerine ve SQL Server log sorunlarına yol açabilir. Doğru yaklaşım ve çözüm yöntemleri hakkında bilgi edinin.