-
Notifications
You must be signed in to change notification settings - Fork 12
Creating event handlers
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:
- When the event handler will be run, either Before, During or After the SaveChanges/SaveChangesAsycn method is run (see this doc explaining the three event types).
- The event class that will trigger it.
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);- You can have multiple event handlers for an event - all of them will be run.
- If an event handler can't be found for the event, then it throws an exception.
- Async event handlers will only be run when
SaveChangesAsyncis called (If you useSaveChangesand the only matching event handlers are async, then it throws an exception but let you know there was an async but it couldn't run it). - 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.MyEventHandlerandMyEventHandlerAsync. If you don't do that then BOTH the sync and async handlers will be called. - It should be obvious, but you must not call SaveChanges/SaveChangesAsync inside a event handler - it would cause a circular call.
There are three types of events: Before, During and After, and each type has a sync and async version.
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.
public interface IBeforeSaveEventHandler<in T> where T : IEntityEvent
{
IStatusGeneric Handle(object callingEntity, T domainEvent);
}public interface IBeforeSaveEventHandlerAsync<in T> where T : IEntityEvent
{
Task<IStatusGeneric> HandleAsync(object callingEntity, T domainEvent);
}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).
public interface IDuringSaveEventHandler<in T> where T : IEntityEvent
{
IStatusGeneric Handle(object callingEntity, T domainEvent, Guid uniqueKey);
}public interface IDuringSaveEventHandlerAsync<in T> where T : IEntityEvent
{
Task<IStatusGeneric> HandleAsync(object callingEntity, T domainEvent, Guid uniqueKey);
}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.
public interface IAfterSaveEventHandler<in T> where T : IEntityEvent
{
void Handle(object callingEntity, T domainEvent);
}public interface IAfterSaveEventHandlerAsync<in T> where T : IEntityEvent
{
Task HandleAsync(object callingEntity, T domainEvent);
}