Skip to content

Creating event handlers

Jon P Smith edited this page Sep 20, 2020 · 3 revisions

An event handler is a class that contains some business code that will be run when a matching event is found in any of the tracked entity classes (i.e. a class that EF Core maps to a database). The entity class must inherit an interface that defines:

As an example of an event handler - notice the interface at the end of line 1

public class OrderReadyToDispatchAfterHandler : IAfterSaveEventHandler<OrderReadyToDispatchEvent>
{
    public void Handle(object callingEntity, OrderReadyToDispatchEvent domainEvent)
    {
        //Send message to dispatch that order has been checked and is ready to go
    }
}

To trigger this event handler an entity needs make the call like this (see Basics of Events for more on how to add an event).

AddEvent(new OrderReadyToDispatchEvent(DispatchDate), EventToSend.AfterSave);

Notes on event handlers

  • You can create normal (sync) event handlers and async event handlers
    • If you call SaveChanges, then only sync event handlers can be run (If you use SaveChanges and the only matching event handlers are async, then it throws an exception with a message saying an async event handler matched that event, but it couldn't run it because it was async).
    • If you call SaveChangesAsync, then both sync and async event handlers can be run
  • If an event handler can't be found for the event, then it throws an exception.
  • If you want both a sync and async version of a event handler, then their class name MUST have the same name, but the async version's class name must end with Async, e.g. MyEventHandler and MyEventHandlerAsync. If you don't do that then BOTH the sync and async handlers will be called.
  • You can have multiple event handlers for a single event - all of them will be run.
  • It should be obvious, but you must not call SaveChanges/SaveChangesAsync inside a event handler - it would cause a circular call.

The six interfaces for event handlers

There are three types of events: Before, During and After, and each type has a sync and async version.

1. Before event handlers

Before event handlers are run before SaveChanges/SaveChangesAsync is called. They can create, update or delete entities and they will be saved when SaveChanges/SaveChangesAsync is called

Before event handlers can return a status (see StatusGeneric library about how this status works). This provides a way for event runners to return a error and stop the database update (a returned status of null is a shortcut to saying the method is successful. See [???] on how to access the errors after the call to SaveChanges/SaveChangesAsync.

sync

public interface IBeforeSaveEventHandler<in T> where T : IEntityEvent
{
    IStatusGeneric Handle(object callingEntity, T domainEvent);
}

async

public interface IBeforeSaveEventHandlerAsync<in T> where T : IEntityEvent
{
    Task<IStatusGeneric> HandleAsync(object callingEntity, T domainEvent);
}

1. During event handlers

During event handlers are run within an transaction, after the call to SaveChanges/SaveChangesAsync but before the the call to the transaction's Commit method is called. You cannot create, update or delete via EF Core because SaveChanges/SaveChangesAsync have already been called (but you could use raw SQL).

During event handlers also return a status (see Before event handlers above). If the status has errors, then the transaction is rolled back.

NOTE: The interfaces is similar, but it has an extra Guid uniqueKey parameter. You can ignore that for this event handler (its there for events handlers that are run pre-SaveChanges in a transaction to handle database retries).

sync

public interface IDuringSaveEventHandler<in T> where T : IEntityEvent
{
    IStatusGeneric Handle(object callingEntity, T domainEvent, Guid uniqueKey);
}

async

public interface IDuringSaveEventHandlerAsync<in T> where T : IEntityEvent
{
    Task<IStatusGeneric> HandleAsync(object callingEntity, T domainEvent, Guid uniqueKey);
}

3. After event handlers

After event handlers are only called after SaveChanges/SaveChangesAsync has finished successfully. These handlers are for sending messages or clearing a cached version of the entity that has been updated.

After event handlers don't return a status because by then the database has been updated.

sync

public interface IAfterSaveEventHandler<in T> where T : IEntityEvent
{
    void Handle(object callingEntity, T domainEvent);
}

async

public interface IAfterSaveEventHandlerAsync<in T> where T : IEntityEvent
{
    Task HandleAsync(object callingEntity, T domainEvent);
}

Clone this wiki locally