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.

comments powered by Disqus

Related