9

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 calling GetHashCode() 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?

Community
  • 1
  • 1
Daniel Trebbien
  • 38,421
  • 18
  • 121
  • 193
  • I am wondering if you found a solution to this problem. I was searching around for an answer as well, and found it odd that there was no per-DbContext hook I could find. My use case is wanting a "read uncommited"-only DbContext and a read-write DbContext. – John Zabroski Jan 27 '17 at 19:58
  • @JohnZabroski: Unfortunately, I did not find an answer to this. – Daniel Trebbien Jan 27 '17 at 22:24
  • I think the only way to do this is to steal the connection string used for the DbConext object, and hash it to decide whether or not to apply context changes. Or some similar "tramp parameter" global variable trick like this – John Zabroski Feb 07 '17 at 22:08

0 Answers0