SkillAgentSearch skills...

EntityFrameworkCore.Triggered

Triggers for EFCore. Respond to changes in your DbContext before and after they are committed to the database.

Install / Use

/learn @EFNext/EntityFrameworkCore.Triggered
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

EntityFrameworkCore.Triggered 👿

Triggers for EF Core. Respond to changes in your DbContext before and after they are committed to the database.

NuGet version (EntityFrameworkCore.Triggered) Build status

NuGet packages

  • EntityFrameworkCore.Triggered NuGet version NuGet
  • EntityFrameworkCore.Triggered.Abstractions NuGet version NuGet
  • EntityFrameworkCore.Triggered.Transactions NuGet version NuGet
  • EntityFrameworkCore.Triggered.Transactions.Abstractions NuGet version NuGet
  • EntityFrameworkCore.Triggered.Extensions NuGet version NuGet

Getting started

  1. Install the package from NuGet
  2. Write triggers by implementing IBeforeSaveTrigger<TEntity> and IAfterSaveTrigger<TEntity>
  3. Register your triggers with your DbContext
  4. View our samples and more samples and a sample application
  5. Check out our wiki for tips and tricks on getting started and being successful.

Example

class StudentSignupTrigger  : IBeforeSaveTrigger<Student> {
    readonly ApplicationDbContext _applicationDbContext;
    
    public class StudentTrigger(ApplicationDbContext applicationDbContext) {
        _applicationDbContext = applicationDbContext;
    }

    public Task BeforeSave(ITriggerContext<Student> context, CancellationToken cancellationToken) {   
        if (context.ChangeType == ChangeType.Added){
            _applicationDbContext.Emails.Add(new Email {
                Student = context.Entity, 
                Title = "Welcome!";,
                Body = "...."
            });
        } 

        return Task.CompletedTask;
    }
}

class SendEmailTrigger : IAfterSaveTrigger<Email> {
    readonly IEmailService _emailService;
    readonly ApplicationDbContext _applicationDbContext;
    public StudentTrigger (ApplicationDbContext applicationDbContext, IEmailService emailservice) {
        _applicationDbContext = applicationDbContext;
        _emailService = emailService;
    }

    public async Task AfterSave(ITriggerContext<Student> context, CancellationToken cancellationToken) {
        if (context.Entity.SentDate == null && context.ChangeType != ChangeType.Deleted) {
            await _emailService.Send(context.Enity);
            context.Entity.SentDate = DateTime.Now;

            await _applicationContext.SaveChangesAsync();
        }
    }
}

public class Student {
    public int Id { get; set; }
    public string Name { get; set; }
    public string EmailAddress { get; set;}
}

public class Email { 
    public int Id { get; set; }  
    public Student Student { get; set; } 
    public DateTime? SentDate { get; set; }
}

public class ApplicationDbContext : DbContext {
    public DbSet<Student> Students { get; set; }
    public DbSet<Email> Emails { get; set; }
}

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddSingleton<EmailService>();
        services
            .AddDbContext<ApplicationContext>(options => {
                options.UseTriggers(triggerOptions => {
                    triggerOptions.AddTrigger<StudentSignupTrigger>();
                    triggerOptions.AddTrigger<SendEmailTrigger>();
                });
            })
            .AddTransient<IEmailService, MyEmailService>();
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    { ... }
}

Related articles

Triggers for Entity Framework Core - Introduces the idea of using EF Core triggers in your codebase

Youtube presentation - Interview by the EF Core team

Trigger discovery

In the given example, we register triggers directly with our DbContext. This is the recommended approach starting from version 2.3 and 1.4 respectively. If you're on an older version then it's recommended to register triggers with your application's DI container instead:

    services
        .AddDbContext<ApplicationContext>(options => options.UseTriggers())
        .AddTransient<IBeforeSaveTrigger<User>, MyBeforeSaveTrigger<User>>();

Doing so will make sure that your triggers can use other services registered in your DI container.

You can also use functionality in EntityFrameworkCore.Triggered.Extensions which allows you to discover triggers that are part of an assembly:

services.AddDbContext<ApplicationContext>(options => options.UseTriggers(triggerOptions => triggerOptions.AddAssemblyTriggers()));
// or alternatively
services.AddAssemblyTriggers();

DbContext pooling

When using EF Core's DbContext pooling, Triggers internally needs to discover the IServiceProvider that was used to obtain a lease on the current DbContext. Thankfully, all that's complexity is hidden and all that's required is a call to AddTriggeredDbContextPool.

services.AddDbContextPool<ApplicationDbContext>(...); // Before
services.AddTriggeredDbContextPool<ApplicationDbContext>(...); // After

Cascading changes (previously called Recursion)

BeforeSaveTrigger<TEntity> supports cascading triggers. This is useful since it allows your triggers to subsequently modify the same DbContext entity graph and have it raise additional triggers. By default this behavior is turned on and protected from infinite loops by limiting the number of cascading cycles. If you don't like this behavior or want to change it, you can do so by:

optionsBuilder.UseTriggers(triggerOptions => {
    triggerOptions.CascadeBehavior(CascadeBehavior.EntityAndType).MaxRecusion(20)
})

Currently there are 2 types of cascading strategies out of the box: NoCascade and EntityAndType (default). The former simply disables cascading, whereas the latter cascades triggers for as long as the combination of the Entity and the change type is unique. EntityAndType is the recommended and default cascading strategy. You can also provide your own implemention.

Inheritance

Triggers support inheritance and sort execution of these triggers based on least concrete to most concrete. Given the following example:

interface IAnimal { }
class Animal : IAnimal { }
interface ICat : IAnimal { }
class Cat : Animal, ICat { }

In this example, triggers will be executed in the order:

  • those for IAnimal,
  • those for Animal
  • those for ICat, and finally
  • Cat itself.

If multiple triggers are registered for the same type, they will execute in order they were registered with the DI container.

Priorities

In addition to inheritance and the order in which triggers are registered, a trigger can also implement the ITriggerPriority interface. This allows a trigger to configure a custom priority (default: 0). Triggers will then be executed in order of their priority (lower goes first). This means that a trigger for Cat can execute before a trigger for Animal, for as long as its priority is set to run earlier. A convenient set of priorities are exposed in the CommonTriggerPriority class.

Error handling

In some cases, you want to be triggered when a DbUpdateException occurs. For this purpose we have IAfterSaveFailedTrigger<TEntity>. This gets triggered for all entities as part of the change set when DbContext.SaveChanges raises a DbUpdateException. The handling method: AfterSaveFailed in turn gets called with the trigger conte

View on GitHub
GitHub Stars593
CategoryData
Updated5h ago
Forks31

Languages

C#

Security Score

100/100

Audited on Mar 28, 2026

No findings