Interception in Entity Framework Core

Right now, Entity Framework Core still does not have all of the features provided by pre-Core versions, I even wrote a post about this. One of this features is the ability to intercept queries, something that was provided by the IDbCommandInterceptor, IDbCommandTreeInterceptor interfaces.

What we have in EF Core 2.0 is interception at the SQL level, and we can only get to this in a rather convoluted way, through the DiagnosticSource mechanism (yes, I do intend to write about it!). So, let’s create a class with three methods, like this:

public class CommandListener
{
[DiagnosticName("Microsoft.EntityFrameworkCore.Database.Command.CommandExecuting")]
public void OnCommandExecuting(DbCommand command, DbCommandMethod executeMethod, Guid commandId, Guid connectionId, bool async, DateTimeOffset startTime)
{
}

[DiagnosticName("Microsoft.EntityFrameworkCore.Database.Command.CommandExecuted")]
public void OnCommandExecuted(object result, bool async)
{
}

[DiagnosticName("Microsoft.EntityFrameworkCore.Database.Command.CommandError")]     public void OnCommandError(Exception exception, bool async)     {     }
}
You will need the Microsoft.Extensions.DiagnosticAdapter NuGet package to compile this.
We hook these methods to EF Core like this:
var listener = ctx.GetService<DiagnosticSource>();
(listener as DiagnosticListener).SubscribeWithAdapter(new CommandListener());
Here, ctx is a DbContext, and we are accessing it’s registered DiagnosticSource service, which is actually a DiagnosticListener, just not registered as such. We just need to add a subscriber, and that’s all!
From the OnCommandExecuted method – by the way, you can call these methods whatever you like, as long as you keep the [DiagnosticName] attributes –, the result parameter will contain either a scalar (in the case of a ExecuteScalar/ExecuteScalarAsync call), a number (for ExecuteNonQuery/ExecuteNonQueryAsync) or a RelationalDataReader object (in the case of a ExecuteReader/ExecuteReaderAsync call). async, of course, will tell you exactly what version of the method was executed.
OnCommandExecuting is more interesting, because here we actually get access to the DbCommand that is about to execute the SQL command, and we can therefore modify its SQL or parameters. Finally, in OnCommandError you can get any exception that is caught during SQL execution.
There are other interception mechanisms, such as IQuerySqlGenerator and IQueryCompiler, and I will probably cover them in a future post. For now, hope this helps! Winking smile

                             

18 Comments

  • Good info about Interception in Entity Framework Core.

  • Brilliant article. The information I have been searching precisely. Even this info clears all the quires about Interception In EF Core of mine. Keep coming with more such informative article. Would love to follow them.

  • Thank you very much for the code. Will try to implement for sure!

  • I don't quite understand when/where to use the hook.
    var listener = ctx.GetService<DiagnosticSource>();
    (listener as DiagnosticListener).SubscribeWithAdapter(new CommandListener());

  • I was also wondering where the best place to use this code is? My use case is I would like to use this code to time how long queries take to run and then log any long running ones. Normally I do all my configuration in the OnConfiguring method but I don't believe we have access to the context here.

  • Brian and Jim: you can use this whenever you have a reference to the context, but not from inside one of the infrastructure method (OnConfiguring, OnModelCreating). You should do this before you actually issue any queries.

  • Thanks Ricardo, I guess what I am scratching my head about is I can't see a way to centralise this so that any dbcontext that gets created can use this code. Ideally I wouldn't want to have to add this code to everywhere I create a dbcontext, that doesn't seem very dry. I'm pretty new to EF so maybe I'm missing something? Thanks for a great article.

  • Hi, James!
    Indeed, I totally understand you... I guess you can have some sort of "factory" method that returns the context after setting this up? Let me give it a thought and I'll get back to you on this!

  • Great article, I was looking for this a long time;
    How can I get access in this methods to the DBContext?

  • Joseph: you can’t.

  • This is good, but you can't really use it for intercepting read queries, since its to "low level".

  • Someone copy/pasted your article.

  • anon: yes, I know, but thanks for reporting. A shame...

  • There is no such method, DbContext.GetService(...) dotnet-core 2.0 - any other ideas? Thanks

  • anon: yes, there is, you just need to add a using declaration for Microsoft.EntityFrameworkCore.Infrastructure.

  • What about catching errors? Can I get informed of SQL exceptions with this?

  • Dan: I just added OnCommandError.

  • Mohammad: well, I know it's tricky, but you *can* change the DbCommand.CommandText and/or its parameters...

Add a Comment

As it will appear on the website

Not displayed

Your website