如何使用per-DbContext状态正确实现DbCommandInterceptor?

Dan*_*ien 6 c# logging entity-framework interceptor entity-framework-6

我正在使用MVC 5教程关注Tom Dykstra的Entity Framework 6 Code First入门. 本教程的第4部分介绍了EF6的连接弹性和命令拦截功能.

作为演示命令拦截使用的一种方式,本教程提供了两个示例DbCommandInterceptor实现.One(SchoolInterceptorLogging)与记录器连接以记录执行的每个SQL命令,执行所花费的时间以及是否存在异常.另一个示例(SchoolInterceptorTransientErrors)模拟瞬态DB错误,以便可以测试连接弹性.

虽然本教程没有特别提到同步问题,但我认为因为单个实例DbCommandInterceptor是通过注册的DbInterception.Add(),所以DbContextDbCommandInterceptor必须同步所使用的状态.例如,SchoolInterceptorLogging应该可能同步使用_stopwatch,并且SchoolInterceptorTransientErrors应该同步_counter.

我想过通过ThreadStaticAttribute或使用线程本地存储ThreadLocal<T>.但是,我不确定这是否足够.假设我的应用程序使用DbContext.SaveChangesAsync()异步保存所有更改.同一个线程是否可以处理两个异步保存操作?如果是这样,那么线程本地存储将无法工作.

另一个想法是在调用上下文中存储每个DbContext状态.但是,使用时CallContext,建议只使用不可变类型.(参见Stephen Cleary的博客文章,Implicit Async Context("AsyncLocal").)

第三个想法是使用a ConcurrentDictionary键,其中键是DbCommandInterceptionContext传递给NonQueryExecuting()/ NonQueryExecuted(),ReaderExecuting()/ ReaderExecuted()或ScalarExecuting()/ ScalarExecuted()的对象,值是一个状态对象.但是,我对这种方法有两个问题:

  • 是个 DbCommandInterceptionContext对于每个保存操作,传递给*Executing()/ *Executed()方法对象是否不同?

    从设置断点ReaderExecuting()和调用GetHashCode()拦截上下文对象,对于每次重试而言,它似乎是不同的,更不用说保存操作(经过测试的EntityFramework版本6.1.3).

  • 这个*Executed()方法总是被调用吗?

    这是为了确保任何加入ConcurrentDictionary*Executing()方法可以随时通过清理*Executed()方法.

那么,应该如何实现DbCommandInterceptorper- DbContextstate?