Interception

Goals

The high-level goal for the interception feature is to allow external code to observe and potentially intercept EF operations. The specific goal for EF6 is to allow generated SQL to be logged without using a wrapping provider. A sub-goal to this is to provide a simple way for applications to log generated SQL.

Interception interfaces

The interception code is built around the concept of interception interfaces. These interfaces inherit from IDbInterceptor and define methods that are called when EF performs some action. The intent is to have one interface per type of object being intercepted. For example, the IDbCommandInterceptor interface defines methods that are called before EF makes a call to ExecuteNonQuery, ExecuteScalar, ExecuteReader, and related methods. Likewise, the interface defines methods that are called when each of these operations completes.

Currently public interception interfaces exist for DbCommand execution and DbCommandTree creation.

The interception context

Looking at the methods defined on any of the interceptor interfaces it is apparent that every call is given an object of type DbInterceptionContext or some type derived from this such as DbCommandInterceptionContext<>. This object contains contextual information about the action that EF is taking. For example, if the action is being taken on behalf of a DbContext, then the DbContext is included in the DbInterceptionContext. Similarly, for commands that are being executed asynchronously, the IsAsync flag is set on DbCommandInterceptionContext.

Collecting this information together into an object keeps the interface methods relatively simple and allows new contextual information to be added in the future with it being a breaking change on the interface.

Caveat

It’s worth noting that the interception context is a best effort to provide contextual information. However, in some corner cases some information that you would expect to be there may not be there. This is because EF has code paths that cannot easily be changed and do not include information that might be expected. For example, when EF makes a call into a provider, the provider has no knowledge of the DbContext being used. If that provider, outside of EF, decides to call ExecuteNonQuery, then two things might happen:

  • First the provider may just make the call directly, avoiding EF interception completely. (This is a consequence of having interception at the EF level rather than lower in the stack. It would be great if interception were lower in the stack, but this is unfortunately outside of the control of the EF team.)
  • If the provider is aware of EF interception then it can dispatch the ExecuteNonQuery call through EF interceptors. This means that any registered interceptor will be notified and can act appropriately. This is what the SQL Server and SQL Server Compact providers do. However, even when a provider does this it is likely that the DbContext being used will not be included in the interception context because the provider has no knowledge of it, and a change to allow this would break the well-defined provider APIs.

Luckily this kind of situation is rare and will likely not be an issue for most applications.

Result handling

The DbInterceptionContext object that is passed to the interceptor methods contains Result, OriginalResult, Exception, and OriginalException properties. These properties are set to null/zero for calls to the interception methods that are called before the operation is executed—e.g. the …Executing methods. If the operation is executed and succeeds, then Result and OriginalResult are set to the result of the operation for calls to interception methods that are called after the operation is executed—e.g. the …Executed methods. Likewise, if the operation throws, then Exception and OriginalException will be set.

Note that not all interception contexts will expose all these properties, only the ones that are applicable to the operation being intercepted.

Suppression of execution

A ”before” interceptor can suppress the execution of the operation that is being intercepted. That is, the interceptor can tell EF not to execute the operation but to instead return some other result. This is done by setting the Result property on interception context in the “before” interceptor.

An example of how this might be used is the command batching that has traditionally been done with a wrapping provider. The interceptor would store the command for later execution as a batch but would “pretend” to EF that the command had executed as normal. Note that it requires more than this to implement batching, but this is an example of how changing the interception result might be used.

In a similar way, a “before” interceptor can set the Exception property to tell EF to throw the given exception instead of executing the operation.

Note that the OriginalResult and OriginalException properties are read-only and are only set by EF. OriginalResult will be set to the actual result of executing the operation unless it threw or execution was suppressed, in which case it will be null/default. Likewise, OriginalException is only set if the operation was actually executed and threw.

Changing Result after execution

If an interceptor sets the Result property after the command has executed (e.g. in one of the …Executed methods) then EF will use the changed result instead of the result that was actually returned from the operation. Likewise, if the Exception property is set after execution, then EF will throw the given exception instead of returning the result.

Registering interceptors

Once a class that implements one or more of the interception interfaces has been created it can be registered with EF using the DbInterception class. Note that interceptors can be added or removed at any time. For example:

DbInterception.Add(new NLogCommandInterceptor());

 

It is also possible to register interceptors in DbConfiguration (part of the new code-based configuration feature).

public class MyConfig : DbConfiguration
{
    public MyConfig()
    {
        AddInterceptor(new NLogCommandInterceptor());
    }
}

Dispatching

In addition to methods for the registration of interceptors, the Interception class also has a Dispatch method. This method allows code that is not part of EF to dispatch notifications to interceptors on behalf of EF. This is the mechanism mentioned above that allows providers to let interceptors know that that a command is being executed outside of the control of EF. It would be rare for an application developer to ever need to use the Dispatch API, but in sure rare cases the calls would look like this:

DbInterception.Dispatch.Command.NonQueryAsync(
    myCommand, new DbCommandInterceptionContext<int>());

This line of code will do the following:

  • Make sure that IsAsync is set on the interception context
  • Call NonQueryExecuting on all registered IDbCommandInterceptors
  • Call ExecuteNonQueryAsync on the given command, unless one of the NonQueryExecuting methods suppressed execution as described above
  • Setup continuations on the async task such that NonQueryExecuted is called on all registered IDbCommandInterceptors
  • Make sure that the result task contains the correct value, which may have been changed by one of the interceptors

Context Log property

The DbContext.Database.Log property can be set to a delegate for any method that takes a string. In particular, it can be used with any TextWriter by setting it to the “Write” method of that TextWriter. All SQL generated by the current context will be logged to that writer. For example, to send output to the Console use something like:

using (var context = new BlogContext())
{
    context.Database.Log = Console.Write;
 
    // Your code here...
}

When the Log property is set all of the following will be logged:

  • SQL for all different kinds of commands. For example:
    • Queries, including normal LINQ queries, eSQL queries, and raw queries from methods such as SqlQuery
    • Inserts, updates, and deletes generated as part of SaveChanges
    • Relationship loading queries such as those generated by lazy loading
  • Parameters
  • Whether or not the command is being executed asynchronously
  • A timestamp indicating when the command started executing
  • Whether or not the command completed successfully, failed by throwing an exception, or, for async, was canceled
  • Some indication of the result value
  • The approximate amount of time it took to execute the command

DatabaseLogFormatter

Under the covers the Database.Log property makes use of a DatabaseLogFormatter object. This object effectively binds a IDbCommandInterceptor implementation to a delegate that accepts strings and a DbContext. This means that interception methods on DatabaseLogFormatter are called before and after the execution of commands by EF. These DatabaseLogFormatter methods gather and format log output and send it to the delegate.

Changing what is logged and how it is formatted can be achieved by creating a new class that derives from DbCommandLogger and then overriding methods as appropriate. The most common methods to override are:

  • LogCommand – Override this to change how commands are logged before they are executed. By default LogCommand calls LogParameter for each parameter; you may choose to do the same in your override or handle parameters differently instead.
  • LogResult – Override this to change how the outcome from executing a command is logged.
  • LogParameter – Override this to change the formatting and content of parameter logging.

Once a new DatabaseLogFormatter class has been created it needs to be registered with EF. This is done using code-based configuration. In a nutshell this means creating a new class that derives from DbConfiguration in the same assembly as your DbContext class and then calling SetDatabaseLogFormatter in the constructor of this new class.

Last edited Sep 16, 2013 at 9:38 PM by RoMiller, version 7

Comments

ajcvickers Mar 19 at 6:45 PM 
@tatsean1977 The interception APIs are low-level APIs that allow you to take a different action than would normally have been taken by EF. However, EF keeps running and hence whatever action you take must either satisfy the contact of the method being intercepted or cause an error condition such that EF breaks with an exception. In this case EF is expecting a data reader to be returned, so if you want EF to continue running you must provide a data reader that EF can use instead of the one that would have been created by the call to ExecuteReader. You then set this on the Result property of the interception context in your ReaderExecuting method.

Thanks,
Arthur

tatsean1977 Mar 18 at 6:29 AM 
i am trying to use the command interceptor, but i have a question. I tried to intercept this query:

var query = dbContext.Courses.Max(p => p.CourseID) ;

when the above line of code is executed, the ReaderExecuting is triggered, however, when it goes back to the main program, it goes to the catch section claiming "object reference not set to an instance of an object". I guess i understand why it happens, but my question is how can suppress the operation without throwing an error in the main program in this scenario?

public void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
{
_stopwatch.Restart();
interceptionContext.SuppressExecution();
}

RoMiller Sep 16, 2013 at 9:45 PM 
@HEskandari - The DbCommand interception point is well below the level at which EF is reasoning about entities. It would be possible to flow the entity through in some cases, but at this stage it's not something we are planning to implement. I suspect it may make more sense to reason about entities at an earlier point in the process - and we plan to add more interception points in the future.
What scenario do you have where it would be interesting to know the entity for the command?

HEskandari Aug 23, 2013 at 6:18 AM 
Supposing you intercept the Dbcommand to do something when an entity is saved, I currently can access the command object, but how can I access the entity that cause this command to be executed? I see that all the entities are on the DbContext wrapped by the interception context, but which one actually caused this command that is being executed?

ajcvickers Jul 1, 2013 at 6:52 PM 
@parampavar & @tudor_turcu Most of the interception work did not make it into beta 1. If you want to try it out then consider using a recent nightly build: https://entityframework.codeplex.com/wikipage?title=Nightly%20Builds

Thanks,
Arthur

parampavar Jul 1, 2013 at 5:18 PM 
context.Database.Log seems to be removed. Is there a different way to do this now?

tudor_turcu Jun 28, 2013 at 11:29 AM 
The Log property in context.Database seems to be gone in the latest version (6.0 beta1) ..