We have seen how queries are evaluated and entities are tracked by EF Core. All operations performed on tracked entities are performed on an in memory instance of entity. The modifications are written to database only when SaveChanges or SaveChangesAsync method is called.
When EF core is performing its work, it provides certain callbacks which can be used by application – either for enhancing some functionality or for some additional logging. EF core provides various types of callbacks. In this article, let’s see one of the callbacks provided by EF Core.
What are they ?
As mentioned earlier, when EF core is performing its work, it provides certain call backs. Simplest form of call back in .NET is events. Any class can expose events and allow clients to subscribe to those.
EF core raises certain events when certain operations are performed. Below are the events that are available in DbContext:
- ChangeTracker.Tracked – This event is raised when an entity is attached or added to the context. This can also be raised if a Tracking query returns some entities, because those entities are added to tracker.
- ChangeTracker.StateChanged – This event is raised when an operation is performed on an entity, that caused entity to change its EntityState to another.
- DbContext.SavingChanges – This event is fired in the very beginning when SaveChanges or SaveChangesAsync starts its work.
- DbContext.SavedChanges – This event is fired as a last step in SaveChanges or SaveChangesAsync, if the save operation was successful.
- DbContext.SaveChangesFailed – This event is fired as a last step in SaveChanges or SaveChangesAsync, if the save operation failed.
How to consume the events ?
In order to demonstrate the concept, let’s create a simple .NET core class library –
EventsDemo.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. A method – ToString – is overridden to display id and first name of the student. This method would be used for logging student information to console from the context.
Next, add a new class a UniversityContext class as shown below. This class subscribes to all the events mentioned before. In every event, it writes something to Console. These logs would be helpful to understand the order in which events are called.
The only method which is bit lengthier than others is the method which handles StateChanged event. This method uses new state to populate appropriate field in the entity.
Next, let’s create a console app (
EventsDemoConsole) and add reference to
EventsDemo.Data.EF class library. Now, 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.
Now, if we run the console app, we should be able to see the logs on console as shown below. We see that the entities are added in tracker as soon as Add method is called on first instance of context.
Next statements show that a Tracking query adds result of query in change tracker, thus Tracking event is raised first. Then application performs update and remove operations, which triggers state changed events for those entities.
The only event that is not demonstrated in the above code is SaveChangesFailed, but I am sure this demo was sufficient to understand the concept.
If you want to use these events in your application, there are two important points to remember.
Events are per DbContext Instance
First point is only applicable if your application uses multiple
DbContext instances. The .NET events provided by EF core are associated with Single
So, if your applicable uses multiple
DbContext and you want to handle callbacks from each of them, then you have option to subscribe to each individual
DbContext. This may work if the handling of events is very specific to
But if the application uses logic which is common for all
DbContexts, then specifying association in every
DbContext might not be desirable. Also, it may not be practical for some scenarios. For such cases, EF core provides another construct – a diagnostic listener can be used to get information from all
DbContext from a given process.
No Async (yet)
The events exposed by
ChangeTracker do not yet support
async callback. This means
async operations like file IO, database read/write, API calls, etc. cannot be performed without blocking main thread. Well, it can be performed, but you will have to then block the main thread, which may not be desirable in most of the cases.
I hope you find this information helpful. Let me know your thoughts.