Application Triggers for ASP.NET Core 2.1 Entity Framework Core

Introduction

In this post, I would like to talk about extending your application and your DbContext to run arbitrary code when a save occurs.

The Backstory

While working with quite a few applications that work with databases, especially using entity framework, I noticed the pattern of saving changes to the database and then do something else based on those changes. A few examples of that are as follows:

When the user state changes, reflect that in the UI.

When adding or updating a product, update the stock.

When deleting an entity, do another action like check for validity.

When an entity changes in any way (add, update, delete), send that out to an external service.

These are mostly akin to having database triggers when the data changes, some action needs to be performed, but those actions are not always database related, more as a response to the change in the database, which sometimes is just business logic.

As such, in one of these applications, I found a way to incorporate that behavior and clean up the repetitive code that would follow, while also keeping it maintainable by just registering the triggers into the IoC container of ASP.NET core.

In this post, we will be having a look at the following:

How to extend the DbContext to allow for the triggers

How to register multiple instances into the container using the same interface or base class

How to create entity instances from tracked changes so we can work with concrete items

How to limit our triggers to only fire under certain data conditions

Injecting dependencies into our triggers

Avoiding infinite loops in our triggers

We have a long enough road ahead so let’s get started.

Creating the Triggers Framework

ITrigger Interface

We will start off with the root of our triggers and that is the ITrigger interface.

Let’s break it down member by member and understand what’s with this base class:

The class is a generic type of T, this ensures that the logic that will be running in any of its descendants will only apply to a specific entity that we want to run our trigger against.

The protected TrackedEntities field holds on to the changed entities, both before and after the change so we can run our trigger logic against them.

The abstract method RegisterChangedEntitiesInternal will be overridden in concrete implementations of this class and ensures that given a ChangeTracker, it will return a set of entities we want to work against. This is not to say that it cannot return an empty collection, it’s just that if we opt to implement a trigger via the TriggerBase class, then it’s highly likely we would want to hold onto those instances for later use.

The abstract method TriggerAsyncInternal runs our trigger logic against n entity we saved from the collection.

The public method RegisterChangedEntities ensures that the abstract method RegisterChangedEntitiesInternal is called, then it calls .ToArray() to ensure that if we have an IEnumerable query, that it also actually executes so that we don’t end up with a collection that is updated later on in the process in an invalid state. This is mostly a judgment call on my end because it is easy to forget that IEnumerable queries have a deferred execution mechanic.

The public method TriggerAsync just enumerates over all of the entities calling TriggerAsyncInternal on each one.

Now that we discussed the base class, it’s time we move on to the definition of a TriggerEntityVersion.

The TriggerEntityVersion Class

The TriggerEntityVersion class is a helper class that serves the purpose of housing the old and the new instance of a given entity.

We have two properties of the same type, one representing the Old instance before any modifications were made and the other representing the New state after the modifications have been made.

The factory method CreateFromEntityProperty uses reflection so that we can turn an EntityEntry which into our own entity so it’s easier to work with, since an EntityEntry is not something so easy to interrogate and work with, this will create instances of our entity and copy over the original and current values that are being tracked, but only if they can be written to and are strings or value types (since classes would represent other entities most of the time, excluding owned properties). Additionally, we only look at the properties being tracked.

We will see an example of how this is used in the following section where we see how to implement concrete triggers.

Concrete Triggers

We will be creating two triggers to show off how they can differ and also how to register multiple triggers later on when we do the integration into the ServiceProvider.

Since the instance of the trigger is created via ServiceProvider, we can inject dependencies via its constructor as we did with the ILogger.

The RegisterChangedEntitiesInternal method implements a query on the tracked entities of type ApplicationUser only if they have been modified. We could check for additional conditions but I would suggest doing that after the .Select call so that you can work with actual instances of your entity.

The TriggerAsyncInternal implementation will just log out the new Id of the user (or any other field we might want).

This class is the same as the previous one, this more for example purposes, except it has a different message and also, it will track all changes to ApplicationUser entities regardless of their state.

Registering the Triggers

Now that we have written up our triggers, it’s time to register them. To register multiple implementations of the same interface or base class, all we need to do is make a change in the Startup.ConfigureServices method (or wherever you’re registering your services) as follows:

This way, you can have triggers of differing lifetimes, as many as you want (though they should be in line with the lifetime of your context, else you will get an error), and easy to maintain. You could even have a configuration file to enable at will certain triggers :D.

Modifying the DbContext

Here, I will show two cases which can be useful depending on your requirement. You will also see that the implementation is the same, the difference being a convenience since for simple cases, all you need to do is inherit, for complex cases, you would need to make these changes manually.

Use a Base Class

If your context only inherits from DbContext, then you could use the following base class:

We inject the IServiceProvider so that we can reach out to our triggers.

We override the SaveChangesAsync (same would go for all the other save methods of the context, though this one is the most used nowadays) and implement the changes.

We get the triggers from the ServiceProvider (we could even filter them out for a specific trigger type but it’s better to have them as is cause it keeps it simple)

We run through each trigger and save the entities that have changes according to our trigger registration logic.

We run the actual save inside the database to ensure that everything worked properly (is there a database error, then the trigger would get cancelled due to the exception bubbling)

We then run each trigger.

We return the result as if nothing happened :D.

Keep in mind that given this implementation you wouldn’t want to have a trigger that updates the same entity or you might end up in a loop, so either you must have firm rules for your trigger or just don’t change the same entity inside the trigger.

License

Share

About the Author

When asked, I always see myself as a .Net Developer because of my affinity for the Microsoft platform, though I do pride myself by constantly learning new languages, paradigms, methodologies, and topics. I try to learn as much as I can from a wide breadth of topics from automation to mobile platforms, from gaming technologies to application security.

If there is one thing I wish to impart, that that is this "Always respect your craft, your tests and your QA"

I agree immensely, but we do have business logic that needs to do some action when an entity is changed. as such it's not a classical database trigger, more like responding to changes in the application by convention.

We could call said logic every time we do a change but that would mean code duplication, with this approach you need to implement it once and forget (for the most part) about it.

As an example, when a user updates some state, the application should broadcast that change for real-time applications (as was the case where this was inspired from)