11

The IDbCommandInterceptor interface is not very well documented. And I've only found a few scarce tutorials on it:

And a few SO questions:


These are the suggestions on hooking I've found:

1 - The static DbInterception class:

DbInterception.Add(new MyCommandInterceptor());

2 - Doing the above suggestion in a DbConfiguration class

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

3 - Using the config file:

<entityFramework>
  <interceptors>
    <interceptor type="EFInterceptDemo.MyCommandInterceptor, EFInterceptDemo"/>
  </interceptors>
</entityFramework>

Although I couldn't figure out how to hook the DbConfiguration class to the DbContext, and neither what to put in the type part of the config method. Another example I found seemed to suggest that you write the namespace of a logger:

type="System.Data.Entity.Infrastructure.Interception.DatabaseLogger, EntityFramework"

I noted that DataBaseLogger implements IDisposable, IDbConfigurationInterceptor and
IDbInterceptor. IDbCommandInterceptor also implements IDbInterceptor, so I tried (without success) to format it like this:

type="DataLayer.Logging.MyCommandInterceptor, DataLayer"

And when I called the static DbInterception class directly, it added another interceptor every call. So my quick and dirty solution was to utilize static constructors:

//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() { }
    }
}

But what are the proper/intended ways to do it?

Community
  • 1
  • 1
Aske B.
  • 6,419
  • 8
  • 35
  • 62

2 Answers2

11

I figured out that my DbContext class just needed to have the DbConfigurationType attribute, to attach a configuration at runtime:

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

public class MyDBConfiguration : DbConfiguration {
    public MyDBConfiguration() {
        this.AddInterceptor(new MyCommandInterceptor());
    }
}
Aske B.
  • 6,419
  • 8
  • 35
  • 62
  • I don't think this is thread-safe. What happens if another thread has MyDBConfiguration2 and a similar constructor body? Then both DbContext's will have MyCommandInterceptor registered twice, right? – John Zabroski Jan 27 '17 at 20:28
  • 1
    In your `MyDBConfiguration` constructor, you dont have to use that static class, you have `this.AddInterceptor()` method. – Jan 'splite' K. Feb 23 '17 at 15:47
  • @JohnZabroski Do you mean that DbInterception.Add() isn't thread-safe? [The documentation](https://msdn.microsoft.com/en-us/library/system.data.entity.infrastructure.interception.dbinterception.add(v=vs.113).aspx) doesn't say so, so I'd assume you're right. Jan'splite'K.'s suggestion looks like the logical answer - and I tested it to confirm that it works. Updated answer accordingly. – Aske B. Feb 23 '17 at 16:34
  • Thread-safe may have been a poor/lazy choice of words. Really, the behavior of your code was not idempotent. – John Zabroski Feb 26 '17 at 13:19
  • I agree with @JohnZabroski, I have two Contexts in different parts of the application and I had errors depending on which context was created first - the second complained that the DbConfigurationType was not the same, so all of this config is global – Paul Hatcher Jun 10 '17 at 11:28
  • The documentation says that `DbConfiguration` is applied at the App-Domain level. It also says that `DbConfigurationType` attribute should be used when your `DbContext` is in a separate assembly from your configuration: https://learn.microsoft.com/en-us/ef/ef6/fundamentals/configuring/code-based – Adolfo Perez Apr 30 '19 at 12:52
5

The docs suggests that you can just put it in 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());
}

The important part is that it only get's called once.

Oskar
  • 1,996
  • 1
  • 22
  • 39