All Projects → koenbeuk → EntityFrameworkCore.Triggered

koenbeuk / EntityFrameworkCore.Triggered

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

Programming Languages

C#
18002 projects

Projects that are alternatives of or similar to EntityFrameworkCore.Triggered

BlazorEFCoreMultitenant
Examples of multitenancy using EF Core and Blazor.
Stars: ✭ 67 (-81.44%)
Mutual labels:  entity-framework, efcore, entity-framework-core
Entityframework Plus
Entity Framework Plus extends your DbContext with must-haves features: Include Filter, Auditing, Caching, Query Future, Batch Delete, Batch Update, and more
Stars: ✭ 1,848 (+411.91%)
Mutual labels:  entity-framework, entity-framework-core
Dotnetlabs
.NET Labs -- Show Me the Tips and Tricks and Code
Stars: ✭ 135 (-62.6%)
Mutual labels:  entity-framework, entity-framework-core
LinqBuilder
LinqBuilder is an advanced implementation of the specification pattern specifically targeting LINQ query generation.
Stars: ✭ 34 (-90.58%)
Mutual labels:  entity-framework, entity-framework-core
Sample Dotnet Core Cqrs Api
Sample .NET Core REST API CQRS implementation with raw SQL and DDD using Clean Architecture.
Stars: ✭ 1,273 (+252.63%)
Mutual labels:  entity-framework, entity-framework-core
Blazorwasmefcoreexample
Example of a Blazor WebAssembly project that uses Entity Framework Core on the server for data access.
Stars: ✭ 105 (-70.91%)
Mutual labels:  entity-framework, entity-framework-core
Uno.SQLitePCLRaw.Wasm
An SQLiteRaw WebAssembly provider for SQLitePCLRaw.core
Stars: ✭ 25 (-93.07%)
Mutual labels:  entity-framework, entity-framework-core
Entityframework.docs
Documentation for Entity Framework Core and Entity Framework 6
Stars: ✭ 888 (+145.98%)
Mutual labels:  entity-framework, entity-framework-core
Linq2db.entityframeworkcore
Bring power of Linq To DB to Entity Framework Core projects
Stars: ✭ 166 (-54.02%)
Mutual labels:  entity-framework, entity-framework-core
Onion Architecture Asp.net Core
WhiteApp API solution template which is built on Onion Architecture with all essential feature using .NET 5!
Stars: ✭ 196 (-45.71%)
Mutual labels:  entity-framework, entity-framework-core
Entityframework.commontools
Extensions, Auditing, Concurrency Checks, JSON properties and Transaction Logs for EntityFramework and EFCore
Stars: ✭ 82 (-77.29%)
Mutual labels:  entity-framework, entity-framework-core
Blazor-CRUD
Simple CRUD application using C#/Blazor
Stars: ✭ 25 (-93.07%)
Mutual labels:  entity-framework, entity-framework-core
Localization
🌏 Database Resource Localization for .NET Core with Entity Framework and In Memory Cache
Stars: ✭ 68 (-81.16%)
Mutual labels:  entity-framework, entity-framework-core
Maikebing.entityframeworkcore.taos
Entity, Framework, EF, Core, Data, O/RM, entity-framework-core,TDengine
Stars: ✭ 113 (-68.7%)
Mutual labels:  entity-framework, entity-framework-core
Entityframework.lazyloading
LazyLoading for EF Core
Stars: ✭ 23 (-93.63%)
Mutual labels:  entity-framework, entity-framework-core
Dntidentity
A highly customized sample of the ASP.NET Core Identity
Stars: ✭ 145 (-59.83%)
Mutual labels:  entity-framework, entity-framework-core
Efcorepowertools
Entity Framework Core Power Tools - reverse engineering, migrations and model visualization for EF Core
Stars: ✭ 774 (+114.4%)
Mutual labels:  entity-framework, entity-framework-core
Efcore.pg
Entity Framework Core provider for PostgreSQL
Stars: ✭ 838 (+132.13%)
Mutual labels:  entity-framework, entity-framework-core
Pomelo.entityframeworkcore.mysql
Entity Framework Core provider for MySQL and MariaDB built on top of MySqlConnector
Stars: ✭ 2,099 (+481.44%)
Mutual labels:  entity-framework, entity-framework-core
Efcoresecondlevelcacheinterceptor
EF Core Second Level Cache Interceptor
Stars: ✭ 227 (-37.12%)
Mutual labels:  entity-framework, entity-framework-core

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.

Since EntityFrameworkCore.Triggered 2.0, triggers will be invoked automatically, however this requires EF Core 5.0. If you're stuck with EF Core 3.1 then you can use EntityFrameworkCore.Triggered V1. This requires you to inherit from TriggeredDbContext or handle manual management of trigger sessions.

EntityFrameworkCore.Triggered V3 is now available for those targeting EF Core 6 or later

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 context containing the entity as well as the exception. You may attempt to call DbContext.SaveChanges again from within this trigger. This will not raise triggers that are already raised and only raise triggers that have since become relevant (based on the cascading configuration).

Lifecycle triggers

Starting with version 2.1.0, we added support for "Lifecycle triggers". These triggers are invoked once per trigger type per SaveChanges lifecyle and reside within the EntityFrameworkCore.Triggered.Lifecycles namespace. These can be used to run something before/after all individual triggers have run. Consider the following example:

public BulkReportTrigger : IAfterSaveTrigger<Email>, IAfterSaveCompletedTrigger {
    private List<string> _emailAddresses = new List<string>();
    
    // This may be invoked multiple times within the same SaveChanges call if there are multiple emails
    public Task AfterSave(ITriggerContext<Email> context, CancellationToken cancellationToken) { 
        if (context.ChangeType == ChangeType.Added) { 
            this._emailAddresses.Add(context.Address);
            return Task.CompletedTask;
        }
    }
    
    public Task AfterSaveCompleted(CancellationToken cancellationToken) {
        Console.WriteLine($"We've sent {_emailAddresses.Count()} emails to {_emailAddresses.Distinct().Count()}" distinct email addresses");
        return Task.CompletedTask;
    }
}

Transactions

Many database providers support the concept of a Transaction. By default when using SqlServer with EntityFrameworkCore, any call to SaveChanges will be wrapped in a transaction. Any changes made in IBeforeSaveTrigger<TEntity> will be included within the transaction and changes made in IAfterSaveTrigger<TEntity> will not. However, it is possible for the user to explicitly control transactions. Triggers are extensible and one such extension are Transactional Triggers. In order to use this plugin you will have to implement a few steps:

// OPTIONAL: Enable transactions when configuring triggers (Required ONLY when not using dependency injection)
triggerOptions.UseTransactionTriggers();
...
using var tx = context.Database.BeginTransaction();
var triggerService = context.GetService<ITriggerService>(); // ITriggerService is responsible for creating now trigger sessions (see below)
var triggerSession = triggerService.CreateSession(context); // A trigger session keeps track of all changes that are relevant within that session. e.g. RaiseAfterSaveTriggers will only raise triggers on changes it discovered within this session (through RaiseBeforeSaveTriggers)

try {
    await context.SaveChangesAsync();
    await triggerSession.RaiseBeforeCommitTriggers();    
    await context.CommitAsync();
    await triggerSession.RaiseAfterCommitTriggers();
}
catch {
    await triggerSession.RaiseBeforeRollbackTriggers();
    await context.RollbackAsync();
    await triggerSession.RaiseAfterRollbackTriggers();	
    throw;
}

In this example we were not able to inherit from TriggeredDbContext since we want to manually control the TriggerSession

Custom trigger types

By default we offer 3 trigger types: IBeforeSaveTrigger, IAfterSaveTrigger and IAfterSaveFailedTrigger. These will cover most cases. In addition we offer IRaiseBeforeCommitTrigger and IRaiseAfterCommitTrigger as an extension to further enhance your control of when triggers should run. We also offer support for custom triggers. Let's say we want to react to specific events happening in your context. We can do so by creating a new interface IThisThingJustHappenedTrigger and implementing an extension method for ITriggerSession to invoke triggers of that type. Please take a look at how Transactional triggers are implemented as an example.

Async triggers

Async triggers are fully supported, though you should be aware that if they are fired as a result of a call to the synchronous SaveChanges on your DbContext, the triggers will be invoked and the results waited for by blocking the caller thread as discussed here. This is known as the sync-over-async problem which can result in deadlocks. It's recommended to use SaveChangesAsync to avoid the potential for deadlocks, which is also best practice anyway for an operation that involves network/file access as is the case with an EF Core read/write to the database.

Similar products

  • Ramses: Lifecycle hooks for EF Core. A simple yet effective way of reacting to changes. Great for situations where you simply want to make sure that a property is set before saving to the database. Limited though in features as there is no dependency injection, no async support, no extensibility model and lifecycle hooks need to be implemented on the entity type itself.
  • EntityFramework.Triggers. Add triggers to your entities with insert, update, and delete events. There are three events for each: before, after, and upon failure. A fine alternative to EntityFrameworkCore.Triggered. It has been around for some time and has support for EF6 and boast a decent community. There are plenty of trigger types to opt into including the option to cancel SaveChanges from within a trigger. A big drawback however is that it does not support cascading triggers so that triggers can never be relied on to enforce a domain constraint.
Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].