Entity Framework Interceptors

I’ve recently published two NuGet packages that may be of interest to Entity Framework Code First users. The first is Isg.EntityFramework.Interceptors which provides an interface for intercepting operations during a call to DbContext.SaveChanges() and taking some action before or after. The 2nd package is Isg.EntityFramework.Interceptors.SoftDelete which provides a concrete implementation of an interceptor.

Using the interceptors library involves three steps.

1) Implement an interceptor.

2) Register your interceptors.

3) Subclass InterceptorDbContext

Implementing an Interceptor

Implementing an interceptor is very easy.  The IInterceptor has two methods—Before and After—each of which refer to the SaveChanges() method of DbContext.

 1:     public interface IInterceptor
 2:     {
 3:         void Before(InterceptionContext context);
 4:         void After(InterceptionContext context);
 5:     }

 

The InterceptionContext consists of information your interceptor may need. You get the InterceptorDbContext that is in the process of being saved, the ObjectContext, ObjectStateManager, DbChangeTracker, and list of DbEntityEntry objects that are participating in the operation.

A TypeInterceptor implementation is provided that filters for entities of a certain type. A ChangeInterceptor implementation is provided that filters entities being Added, Modified, or Deleted. The ChangeInterceptor provides Before and After methods that can be overridden for each of those operations.

Register Your Interceptors

Interceptor registration is easy. In the case of the SoftDeleteInterceptor, registration looks like this:

 1: InterceptorProvider.SetInterceptorProvider(
 2:     new DefaultInterceptorProvider(
 3:         new SoftDeleteChangeInterceptor()));

 

InterceptorProvider is a global cache of Interceptor instances that relies on a provider to resolve the instances. In this case, I am manually providing the SoftDeleteInterceptor, but you can plug in a dependency injection framework or any other source of providers that you prefer. If you do this at application startup, any instance of InterceptorDbContext will automatically pull in the Interceptors and apply them during the SaveChanges operation.

Subclass InterceptorDbContext

This one really is as easy as it sounds. Instead of inheriting directly from DbContext, inherit from InterceptorDbContext. For example:

 1:     public class CustomerDbContext : InterceptorDbContext
 2:     {
 3:         public DbSet<Invoice> Invoices { get; set; }
 4:         public DbSet<Customer> Customers { get; set; }
 5: 
 6:         protected override void OnModelCreating(DbModelBuilder modelBuilder)
 7:         {
 8:             base.OnModelCreating(modelBuilder);
 9: 
 10:             modelBuilder.Entity<Invoice>()
 11:                .HasKey(i => i.InvoiceId)
 12:                ;
 13:             modelBuilder.Entity<Invoice>()
 14:                 .HasRequired(invoice => invoice.Customer)
 15:                 .WithMany(customer => customer.Invoices)
 16:                 .HasForeignKey(i => i.CustomerId)
 17:                 ;
 18: 
 19:             modelBuilder.Entity<Customer>()
 20:                .HasKey(customer => customer.CustomerId)
 21:                ;
 22:         }
 23:     }

InterceptorDbContext will build the InterceptionContext object and pass it to your interceptors Before and After the SaveChanges method is called.

Your Input Matters

The current version of the libraries are 0.2.1. If there are other commonly used interceptor implementations that should be implemented I’ll be happy to create packages for those. If you have other feature requests for the package I’d love to hear them.

8 thoughts on “Entity Framework Interceptors”

  1. So, I’ve implemented the interceptor. If I have a look at the entries in the InterceptionContext whilst in the “before” method, I can see those which were Added/Deleted/Modified. However, looking at the entries in “After”, the state reverts to “Unchanged”. My dilemma is that I need to store the primary keys of those entities that have been changed. The Id is only generated after insert – hence me interrogating them in “After”. Can you offer any guidance?

  2. This is awesome! I have a question though, where’s the best place to put the Interceptor registration code? In my particular case i’m working on an MVC app but the interceptor stuff is in a different data layer assembly.

    1. I usually have a Bootstrapper class in my Application assembly (in your case the MVC project). I let the Bootstrapper setup and configure things like this. If that doesn’t work for you (e.g., if your interceptors are not visible to the MVC assmebly), you’ll either need to create a Bootstrapper in the Data assembly or use a static constructor on your DbContext to create and register the interceptors. Any of these approaches should work, though I personally like it better when the Application assembly does this work.

  3. I wish I’d found this sooner. I’ve spent hours reproducing something very similar to, but less elegant than your approach!

    One suggestion for another commonly used property which could be intercepted is Created/Modified timestamps.

    I’d also be really interested to see if there’s a similar way to intercept queries. So for example, if I implement ISoftDelete, to avoid having to include .where(!IsDeleted) every time I query that model.

    1. Oh and another thing this could be used for; audit logs. One could possibly even go a step further and use it for versioning

    2. Thanks for the feedback! I agree with you about audit fields. I even demo’d how you might do that here: http://iextendable.com/2010/10/20/implementing-ef4-change-interceptors/. The reason I didn’t include that out of the box is that there is an enormous amount of variation on how people like to do the auditing. I didn’t feel I had a handle on the most common implementations, but the package certainly lets you add any kind of interception you wish.

      Query interception would be great, but I’m not sure how to accomplish it in EF. You could use a Generic Repository implementation and add prefilter the IQueryable, but you are still susceptible to pulling in soft-deleted rows from navigation properties.

Leave a Reply