Entity Framework core exposes some events via DbContext and we have seen how those events can be used. In this article, we are going to have a look at another concept – Interceptors.
What is an EF Core Interceptor ?
As the name suggests, an interceptor can be used to intercept the EF core operations. This feature can be useful for enabling logging and auditing the operations performed by EF core. Interestingly, the interceptors can also be used to modify the operations or EF core operations can also be suppressed altogether via an interceptor.
Note that the interceptors themselves do not log anything. It is just a mechanism to customize the EF core operations. The custom code can either log the data to some store or it can just enhance the queries, without logging anything.
If you wish to enable logging, then Simple logging or Microsoft.Extensions.Logging can be better choices instead of adding a new interceptor.
How do they work ?
IInterceptor is the god interface of all interceptors. This interface has been used as base interface for other interceptor interfaces. Below are some of the interceptor interfaces and abstract classes which ultimately derive from IInterceptor:
- IDbCommandInterceptor – an interface which can be used to intercept database commands and modify them if required.
- DbCommandInterceptor – this is an abstract class which can be used instead of interface. If this is used to create a new interceptor, then only few methods can be overridden by new class. Rest of the methods would use the default implementation provided by this class.
- IDbConnectionInterceptor – an interface which can be used to intercept connection related operations.
- DbConnectionInterceptor – this is an abstract class, can be used to derive new connection interceptor instead of using interface.
- IDbTransactionInterceptor – an interface for intercepting transaction related operations.
- DbTransactionInterceptor – this is abstract class for deriving new transaction interceptor.
- ISaveChangesInterceptor – interface for intercepting save changes operation
- SaveChangesInterceptor – an abstract class for creating new save changes interceptor.
The interfaces (and corresponding abstract too) provide both sync
and async
versions of the “hook-points”. If you wish to use interface, you will have to implement all the methods brought by the interface. Probably, it may be better to use abstract class if you wish to use only one of the hook-point.
All of these abstract classes have many virtual methods. For every operation that EF core performs, the abstract classes provide a pre and a post method for interception. Although the class is marked as abstract, it does not have any abstract method. All the methods in those classes are virtual. So the derived class can choose which method to override.
How to attach an Interceptor to DbContext?
Once a custom interceptor is created, it must be attached with a DbContext
. It can be done by calling AddInterceptors from OnConfiguring method of DbContext
.
Demo
For the demo, let’s create a simple DbCommandInterceptor. This custom interceptor would add the order by clause to the query. Note that order by clause can be specified direclty in the LINQ query. The example is just to demonstrate how intercept can be used.
EF Core Class Library
Let’s create a simple .NET core class library – EFCoreInterceptorsDemo.Data.EF
. Add references to two NuGet packages in that library:
- Microsoft.EntityFrameworkCore.Design – for enabling dotnet CLI EF Core tools on the class library.
- Microsoft.EntityFrameworkCore.SqlServer – as we are going to use SQL Server database
Then add a simple EF Core model – Student – for holding very basic information related to university students.
Custom Interceptor
Now, order by clause makes sense only if the query is a SELECT query and it returns more than one records. That means in custom interceptor, we will have to identify if order by clause is required or not.
One way is to check the DbCommand.CommandText to see if query is a select query. But this approach may have its own problems and may fail for many cases. Another approach is to use query tagging. A LINQ query can be tagged to let interceptor know if order by clause is required or not. And if the tag is found, then the interceptor will add the clause, otherwise interceptor will skip the query.
For the purpose of this demo, we are going to add some records to the database. and immediately we are going to retrieve them. So, the records will always be fetched in the order in which they were inserted. Hence, in order to see the difference in the resultset returned, let’s add order by descending clause via interceptor.
Below is the demo code for interceptor. Add class this to EFCoreInterceptorsDemo.Data.EF
class library.
Demo DbContext
Next, add a new class a UniversityContext class as shown below. The class overrides OnConfiguring method and attaches custom interceptor to the context.
Console App
Next, let’s create a console app (
) and add reference to EFCoreInterceptorsDemo
class library. EFCoreInterceptorsDemo
.Data.EF
Below is how the console app looks like. It creates two instances of context. First instance is used just to add two new entities to database. Second instance is used to perform edit and delete operation and both operations are saved together.
Run and Verify
Now, let’s run the console app to check if we get to see the expected result. The console app should show the output as shown in below snapshot.
Design Considerations and Limitations
Like .NET Events, the interceptors are also attached to a single instance of DbContext
. So if you have multiple DbContext
s in the application, you may want to explore other options like Diagnostic Listener.
Although SaveChangesInterceptor provides a way to intercept save changes operation, many times it may be easier to override the SaveChanges or SaveChangesAsync method in the DbContext
.
I hope you find this helpful. Let me know your thoughts.