将IDbInterceptor钩入EntityFramework DbContext只需一次。

12

IDbCommandInterceptor interface documentation is inadequate, and only a few tutorials on it are available, such as:

Additionally, a few Stack Overflow questions:


Here are the suggestions for hooking:

1 - The static DbInterception class:

DbInterception.Add(new MyCommandInterceptor());

2 - 在一个DbConfiguration类中执行上述建议

public class MyDBConfiguration : DbConfiguration {
    public MyDBConfiguration() {
        DbInterception.Add(new MyCommandInterceptor());
    }
}

3 - 使用配置文件:

<entityFramework>
  <interceptors>
    <interceptor type="EFInterceptDemo.MyCommandInterceptor, EFInterceptDemo"/>
  </interceptors>
</entityFramework>
虽然我无法弄清楚如何将 DbConfiguration 类连接到 DbContext,也不知道在配置方法的 type 部分中要放什么。我找到了另一个示例,似乎建议您编写记录器的名称空间:
type="System.Data.Entity.Infrastructure.Interception.DatabaseLogger, EntityFramework"

我注意到 DataBaseLogger 实现了IDisposableIDbConfigurationInterceptor
IDbInterceptorIDbCommandInterceptor 也实现了IDbInterceptor,所以我尝试将其格式化为这样(但没有成功):

type="DataLayer.Logging.MyCommandInterceptor, DataLayer"

当我直接调用静态 DbInterception 类时,每次调用它都会添加另一个拦截器。所以我的快速而简单的解决方案是利用静态构造函数:

//This partial class is a seperate file from the Entity Framework auto-generated class,
//to allow dynamic connection strings
public partial class MyDbContext // : DbContext
{
    public Guid RequestGUID { get; private set; }

    public MyDbContext(string nameOrConnectionString) : base(nameOrConnectionString)
    {
        DbContextListeningInitializer.EnsureListenersAdded();

        RequestGUID = Guid.NewGuid();
        //Database.Log = m => System.Diagnostics.Debug.Write(m);
    }

    private static class DbContextListeningInitializer
    {
        static DbContextListeningInitializer() //Threadsafe
        {
            DbInterception.Add(new MyCommandInterceptor());
        }
        //When this method is called, the static ctor is called the first time only
        internal static void EnsureListenersAdded() { }
    }
}

但是,正确/预期的方法是什么?

2个回答

11

我发现我的DbContext类只需要有DbConfigurationType属性,就可以在运行时附加配置:

[DbConfigurationType(typeof(MyDBConfiguration))]
public partial class MyDbContext // : DbContext
{
    public MyDbContext(string nameOrConnectionString) : base(nameOrConnectionString)
    { }
}

public class MyDBConfiguration : DbConfiguration {
    public MyDBConfiguration() {
        this.AddInterceptor(new MyCommandInterceptor());
    }
}

我认为这不是线程安全的。如果另一个线程有MyDBConfiguration2并且具有类似的构造函数体,则会发生什么情况?那么两个DbContext都将注册MyCommandInterceptor两次,对吧? - John Zabroski
1
在你的 MyDBConfiguration 构造函数中,你不必使用那个静态类,你可以使用 this.AddInterceptor() 方法。 - Jan 'splite' K.
@JohnZabroski你的意思是DbInterception.Add()不是线程安全的吗? 文档没有这样说,所以我认为你是正确的。Jan'splite'K.'的建议看起来是合乎逻辑的答案 - 我测试了一下并确认它可以工作。根据更新了答案。 - Aske B.
“Thread-safe” 可能是一个不太恰当/懒惰的措辞。实际上,你的代码行为并不幂等。 - John Zabroski
我同意@JohnZabroski的观点,我在应用程序的不同部分有两个上下文,并且根据第一个创建的上下文会出现错误 - 第二个抱怨DbConfigurationType不同,因此所有这些配置都是全局的。 - Paul Hatcher
文档说明DbConfiguration应用于App-Domain级别。它还指出,当您的DbContext与配置不在同一个程序集中时,应使用DbConfigurationType属性:https://learn.microsoft.com/en-us/ef/ef6/fundamentals/configuring/code-based - Adolfo Perez

6

文档建议你可以将它放在Application_Start中:

protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();
    FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
    RouteConfig.RegisterRoutes(RouteTable.Routes);
    BundleConfig.RegisterBundles(BundleTable.Bundles);
    DbInterception.Add(new SchoolInterceptorTransientErrors());
    DbInterception.Add(new SchoolInterceptorLogging());
}

重要的是它只会被调用一次。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接