-
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 create normal (sync) event handlers and async event handlers
- If you call
SaveChanges, then only sync event handlers can be run (If you useSaveChangesand 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 you call
- 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.MyEventHandlerandMyEventHandlerAsync. 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.
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);
}