I am following Tom Dykstra's Getting Started with Entity Framework 6 Code First using MVC 5 tutorial. Part 4 of this tutorial covers EF6's connection resiliency and command interception features.
As a way of demonstrating the use of command interception, the tutorial provides two example DbCommandInterceptor
implementations. One (SchoolInterceptorLogging
) interfaces with a logger to record each SQL command that is executed, how much time it took to execute, and whether there was an exception. The other example (SchoolInterceptorTransientErrors
) simulates transient DB errors so that connection resiliency can be tested.
Although the tutorial does not specifically mention the issue of synchronization, I assume that because a single instance of the DbCommandInterceptor
is registered via DbInterception.Add()
, that any per-DbContext
state utilized by the DbCommandInterceptor
must be synchronized. For example, SchoolInterceptorLogging
should probably synchronize use of the _stopwatch
, and SchoolInterceptorTransientErrors
should probably synchronize the _counter
.
I thought about using thread-local storage via ThreadStaticAttribute
or ThreadLocal<T>
. However, I am not sure that this is sufficient. Suppose that my application uses DbContext.SaveChangesAsync()
to asynchronously save all changes. Isn't it possible for the same thread to handle two asynchronous save operations? If so, then thread-local storage wouldn't work.
Another idea is to store per-DbContext
state in the call context. However, when using the CallContext
, it is recommended that only immutable types should be used. (See Stephen Cleary's blog post, Implicit Async Context ("AsyncLocal").)
A third idea is to use a ConcurrentDictionary
where the keys are the DbCommandInterceptionContext
object passed to NonQueryExecuting()/NonQueryExecuted(), ReaderExecuting()/ReaderExecuted(), or ScalarExecuting()/ScalarExecuted(), and the values are a state object. However, I have two questions about this approach:
Is the
DbCommandInterceptionContext
object that is passed to the*Executing()
/*Executed()
method distinct for each save operation?From setting a breakpoint in
ReaderExecuting()
and callingGetHashCode()
on the interception context object, it appears to be distinct for each retry let alone save operation (tested EntityFramework version 6.1.3).Will the
*Executed()
method always be called?This is to make sure that anything added to the
ConcurrentDictionary
in the*Executing()
method can always be cleaned up by the*Executed()
method.
So, how should a DbCommandInterceptor
with per-DbContext
state be implemented?