Sending MediatR Notifications Immediately After Saving an Entity with Entity Framework Core
Download full source code.
A few weeks ago I wrote a post about accessing objects just after they are saved by Entity Framework, in that post I printed a line to the console for each entity that was saved.
I said that would show how this can be useful when you want to send out a notification that an entity has been created/updated. In this post, I show how to send MediatR notifications right after an entity has been saved.
I’m not going to explain how to use MediatR, there is good documentation for that available here, and I have written a few posts about it too. You can find the handler in the ProductNotificationHandler.cs
file, it is very simple, it just prints a line to the console.
What I will show is how to override the SaveChangesAsync
method of the DbContext
to send out a notification right after the entity is saved.
The full source code can be found in the attached zip file.
The Seeder
Each time the application starts up it deletes and recreates the database, then calls a seed extension method on the context. It creates 10 products and adds them to the database.
1public static async Task SeedAsync(this SalesContext salesContext)
2{
3 if (!salesContext.Products.Any())
4 {
5 var faker = new Faker<Product>()
6 // .RuleFor(p => p.ProductId, f => f.IndexFaker) // don't include, the database will generate this
7 .RuleFor(p => p.Name, f => f.Commerce.ProductName())
8 .RuleFor(p => p.Description, f => f.Commerce.ProductDescription())
9 .RuleFor(p => p.Price, f => f.Random.Decimal(0, 100))
10 .RuleFor(p => p.SKU, f => f.Commerce.Ean13())
11 .RuleFor(p => p.Code, f => f.Commerce.Ean8())
12 .RuleFor(p => p.ProductCategory, f => f.PickRandom<ProductCategory>());
13
14 var products = faker.Generate(10);
15 salesContext.Products.AddRange(products);
16 await salesContext.SaveChangesAsync();
17 }
18}
The DbContext
Open the data/SalesContext.cs
file.
You will see that IMediatr
is passed to the constructor, the created instance will be used to send out notifications. Mediatr is registered with the service collection in Program.cs
, line 13.
I added a SaveChangeAsync
method with the following code -
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
int resultOfSave = await base.SaveChangesAsync(cancellationToken);
var products = ChangeTracker.Entries().Where(ee => ee.Entity is Product).Select(ee => ee.Entity as Product);
foreach (var product in products)
{
await _mediator.Publish(new ProductNotification(product));
}
return resultOfSave;
}
Line 3 performs calls to the base SaveChangesAsync
method, this is the method that actually saves the entity to the database. Rather than return immediately, it stores the result in an int.
Line 5 gets the Product
entities that have been saved.
Lines 6-9 loop through the products and sends out a notification for each one.
You can find the rest of the code in the attached zip, when you run it, it will seed a database. Look in the appsettings.Development.json
file for the database connection string.
There is a tests.rest
file in the zip file, use it to send Post
and Put
requests to the Products
endpoint with Rider and Rest Client extension for VS Code.
As the entity is created/updated you will see the notifications in the console.
Conclusion
This is a simple example of sending out notifications after an entity has been saved. This helps decouple the code that saves the entity from the code that needs to react to the change.
Download full source code.