Skip to content

Commit 96b2183

Browse files
Update README for AutoGuru fork and .NET 9
Convert README to reflect the AutoGuru-maintained fork targeting .NET 9 / EF Core 9. Update package names, NuGet badges and links to use AutoGuru.* packages and the autoguru-au GitHub org. Clarify trigger invocation (ISaveChangesInterceptor), update transaction/extension package links, fix various sample code formatting/typos, and add an acknowledgement noting the project is a fork of koenbeuk/EntityFrameworkCore.Triggered.
1 parent 25bb8ad commit 96b2183

File tree

1 file changed

+40
-37
lines changed

1 file changed

+40
-37
lines changed

README.md

Lines changed: 40 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,44 @@
1-
# EntityFrameworkCore.Triggered 👿
1+
# EntityFrameworkCore.Triggered
22
Triggers for EF Core. Respond to changes in your DbContext before and after they are committed to the database.
33

4-
[![NuGet version (EntityFrameworkCore.Triggered)](https://img.shields.io/nuget/v/EntityFrameworkCore.Triggered.svg?style=flat-square)](https://www.nuget.org/packages/EntityFrameworkCore.Triggered/)
5-
[![Build status](https://github.com/koenbeuk/EntityFrameworkCore.Triggered/workflows/.NET%20Core/badge.svg)](https://github.com/koenbeuk/EntityFrameworkCore.Triggered/actions?query=workflow%3A%22.NET+Core%22)
4+
This is the [AutoGuru](https://github.com/autoguru-au)-maintained fork, targeting .NET 9 / EF Core 9. Published as `AutoGuru.*` NuGet packages.
5+
6+
[![NuGet version (AutoGuru.EntityFrameworkCore.Triggered)](https://img.shields.io/nuget/v/AutoGuru.EntityFrameworkCore.Triggered.svg?style=flat-square)](https://www.nuget.org/packages/AutoGuru.EntityFrameworkCore.Triggered/)
7+
[![Build status](https://github.com/autoguru-au/EntityFrameworkCore.Triggered/workflows/.NET%20Core/badge.svg)](https://github.com/autoguru-au/EntityFrameworkCore.Triggered/actions?query=workflow%3A%22.NET+Core%22)
68

79
## NuGet packages
8-
- EntityFrameworkCore.Triggered [![NuGet version](https://img.shields.io/nuget/v/EntityFrameworkCore.Triggered.svg?style=flat-square)](https://www.nuget.org/packages/EntityFrameworkCore.Triggered/) [![NuGet](https://img.shields.io/nuget/dt/EntityFrameworkCore.Triggered.svg?style=flat-square)](https://www.nuget.org/packages/EntityFrameworkCore.Triggered/)
9-
- EntityFrameworkCore.Triggered.Abstractions [![NuGet version](https://img.shields.io/nuget/v/EntityFrameworkCore.Triggered.Abstractions.svg?style=flat-square)](https://www.nuget.org/packages/EntityFrameworkCore.Triggered.Abstractions/) [![NuGet](https://img.shields.io/nuget/dt/EntityFrameworkCore.Triggered.Abstractions.svg?style=flat-square)](https://www.nuget.org/packages/EntityFrameworkCore.Triggered.Abstractions/)
10-
- EntityFrameworkCore.Triggered.Transactions [![NuGet version](https://img.shields.io/nuget/v/EntityFrameworkCore.Triggered.Transactions.svg?style=flat-square)](https://www.nuget.org/packages/EntityFrameworkCore.Triggered.Transactions/) [![NuGet](https://img.shields.io/nuget/dt/EntityFrameworkCore.Triggered.Transactions.svg?style=flat-square)](https://www.nuget.org/packages/EntityFrameworkCore.Triggered.Transactions/)
11-
- EntityFrameworkCore.Triggered.Transactions.Abstractions [![NuGet version](https://img.shields.io/nuget/v/EntityFrameworkCore.Triggered.Transactions.Abstractions?style=flat-square)](https://www.nuget.org/packages/EntityFrameworkCore.Triggered.Transactions.Abstractions/) [![NuGet](https://img.shields.io/nuget/dt/EntityFrameworkCore.Triggered.Transactions.Abstractions?style=flat-square)](https://www.nuget.org/packages/EntityFrameworkCore.Triggered.Transactions.Abstractions/)
12-
- EntityFrameworkCore.Triggered.Extensions [![NuGet version](https://img.shields.io/nuget/v/EntityFrameworkCore.Triggered.Extensions?style=flat-square)](https://www.nuget.org/packages/EntityFrameworkCore.Triggered.Extensions/) [![NuGet](https://img.shields.io/nuget/dt/EntityFrameworkCore.Triggered.Extensions?style=flat-square)](https://www.nuget.org/packages/EntityFrameworkCore.Triggered.Extensions/)
10+
- AutoGuru.EntityFrameworkCore.Triggered [![NuGet version](https://img.shields.io/nuget/v/AutoGuru.EntityFrameworkCore.Triggered.svg?style=flat-square)](https://www.nuget.org/packages/AutoGuru.EntityFrameworkCore.Triggered/) [![NuGet](https://img.shields.io/nuget/dt/AutoGuru.EntityFrameworkCore.Triggered.svg?style=flat-square)](https://www.nuget.org/packages/AutoGuru.EntityFrameworkCore.Triggered/)
11+
- AutoGuru.EntityFrameworkCore.Triggered.Abstractions [![NuGet version](https://img.shields.io/nuget/v/AutoGuru.EntityFrameworkCore.Triggered.Abstractions.svg?style=flat-square)](https://www.nuget.org/packages/AutoGuru.EntityFrameworkCore.Triggered.Abstractions/) [![NuGet](https://img.shields.io/nuget/dt/AutoGuru.EntityFrameworkCore.Triggered.Abstractions.svg?style=flat-square)](https://www.nuget.org/packages/AutoGuru.EntityFrameworkCore.Triggered.Abstractions/)
12+
- AutoGuru.EntityFrameworkCore.Triggered.Transactions [![NuGet version](https://img.shields.io/nuget/v/AutoGuru.EntityFrameworkCore.Triggered.Transactions.svg?style=flat-square)](https://www.nuget.org/packages/AutoGuru.EntityFrameworkCore.Triggered.Transactions/) [![NuGet](https://img.shields.io/nuget/dt/AutoGuru.EntityFrameworkCore.Triggered.Transactions.svg?style=flat-square)](https://www.nuget.org/packages/AutoGuru.EntityFrameworkCore.Triggered.Transactions/)
13+
- AutoGuru.EntityFrameworkCore.Triggered.Transactions.Abstractions [![NuGet version](https://img.shields.io/nuget/v/AutoGuru.EntityFrameworkCore.Triggered.Transactions.Abstractions?style=flat-square)](https://www.nuget.org/packages/AutoGuru.EntityFrameworkCore.Triggered.Transactions.Abstractions/) [![NuGet](https://img.shields.io/nuget/dt/AutoGuru.EntityFrameworkCore.Triggered.Transactions.Abstractions?style=flat-square)](https://www.nuget.org/packages/AutoGuru.EntityFrameworkCore.Triggered.Transactions.Abstractions/)
14+
- AutoGuru.EntityFrameworkCore.Triggered.Extensions [![NuGet version](https://img.shields.io/nuget/v/AutoGuru.EntityFrameworkCore.Triggered.Extensions?style=flat-square)](https://www.nuget.org/packages/AutoGuru.EntityFrameworkCore.Triggered.Extensions/) [![NuGet](https://img.shields.io/nuget/dt/AutoGuru.EntityFrameworkCore.Triggered.Extensions?style=flat-square)](https://www.nuget.org/packages/AutoGuru.EntityFrameworkCore.Triggered.Extensions/)
1315

1416
## Getting started
15-
1. Install the package from [NuGet](https://www.nuget.org/packages/EntityFrameworkCore.Triggered)
17+
1. Install the package from [NuGet](https://www.nuget.org/packages/AutoGuru.EntityFrameworkCore.Triggered)
1618
2. Write triggers by implementing `IBeforeSaveTrigger<TEntity>` and `IAfterSaveTrigger<TEntity>`
1719
3. Register your triggers with your DbContext
18-
4. View our [samples](https://github.com/koenbeuk/EntityFrameworkCore.Triggered/tree/master/samples) and [more samples](https://github.com/koenbeuk/EntityFrameworkCore.Triggered.Samples) and [a sample application](https://github.com/koenbeuk/EntityFrameworkCore.BookStoreSampleApp)
19-
5. Check out our [wiki](https://github.com/koenbeuk/EntityFrameworkCore.Triggered/wiki) for tips and tricks on getting started and being successful.
20-
21-
> 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](https://www.nuget.org/packages/EntityFrameworkCore.Triggered/1.4.0). This requires you to inherit from `TriggeredDbContext` or handle manual management of trigger sessions.
20+
4. View our [samples](https://github.com/autoguru-au/EntityFrameworkCore.Triggered/tree/master/samples)
21+
5. Check out our [wiki](https://github.com/autoguru-au/EntityFrameworkCore.Triggered/wiki) for tips and tricks on getting started and being successful.
2222

23-
> [EntityFrameworkCore.Triggered V3](https://www.nuget.org/packages/EntityFrameworkCore.Triggered/3.0.0) is now available for those targeting EF Core 6 or later
23+
> This fork targets .NET 9 / EF Core 9. Triggers are invoked automatically via EF Core's `ISaveChangesInterceptor`.
2424
2525
### Example
2626
```csharp
2727
class StudentSignupTrigger : IBeforeSaveTrigger<Student> {
2828
readonly ApplicationDbContext _applicationDbContext;
29-
29+
3030
public class StudentTrigger(ApplicationDbContext applicationDbContext) {
3131
_applicationDbContext = applicationDbContext;
3232
}
3333

34-
public Task BeforeSave(ITriggerContext<Student> context, CancellationToken cancellationToken) {
34+
public Task BeforeSave(ITriggerContext<Student> context, CancellationToken cancellationToken) {
3535
if (context.ChangeType == ChangeType.Added){
3636
_applicationDbContext.Emails.Add(new Email {
37-
Student = context.Entity,
37+
Student = context.Entity,
3838
Title = "Welcome!";,
3939
Body = "...."
4040
});
41-
}
41+
}
4242

4343
return Task.CompletedTask;
4444
}
@@ -68,9 +68,9 @@ public class Student {
6868
public string EmailAddress { get; set;}
6969
}
7070

71-
public class Email {
72-
public int Id { get; set; }
73-
public Student Student { get; set; }
71+
public class Email {
72+
public int Id { get; set; }
73+
public Student Student { get; set; }
7474
public DateTime? SentDate { get; set; }
7575
}
7676

@@ -105,7 +105,7 @@ public class Startup
105105
[Youtube presentation](https://youtu.be/Gjys0Yebobk?t=671) - Interview by the EF Core team
106106

107107
### Trigger discovery
108-
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:
108+
In the given example, we register triggers directly with our DbContext. This is the recommended approach. If you prefer, you can register triggers with your application's DI container instead:
109109

110110
```csharp
111111
services
@@ -115,7 +115,7 @@ In the given example, we register triggers directly with our DbContext. This is
115115

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

118-
You can also use functionality in [EntityFrameworkCore.Triggered.Extensions](https://www.nuget.org/packages/EntityFrameworkCore.Triggered.Extensions/) which allows you to discover triggers that are part of an assembly:
118+
You can also use functionality in [AutoGuru.EntityFrameworkCore.Triggered.Extensions](https://www.nuget.org/packages/AutoGuru.EntityFrameworkCore.Triggered.Extensions/) which allows you to discover triggers that are part of an assembly:
119119

120120
```csharp
121121
services.AddDbContext<ApplicationContext>(options => options.UseTriggers(triggerOptions => triggerOptions.AddAssemblyTriggers()));
@@ -150,34 +150,34 @@ interface ICat : IAnimal { }
150150
class Cat : Animal, ICat { }
151151
```
152152

153-
In this example, triggers will be executed in the order:
154-
* those for `IAnimal`,
153+
In this example, triggers will be executed in the order:
154+
* those for `IAnimal`,
155155
* those for `Animal`
156-
* those for `ICat`, and finally
157-
* `Cat` itself.
156+
* those for `ICat`, and finally
157+
* `Cat` itself.
158158

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

161161
### Priorities
162162
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.
163163

164164
### Error handling
165-
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).
165+
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).
166166

167167
### Lifecycle triggers
168168
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:
169169
```csharp
170170
public BulkReportTrigger : IAfterSaveTrigger<Email>, IAfterSaveCompletedTrigger {
171171
private List<string> _emailAddresses = new List<string>();
172-
172+
173173
// This may be invoked multiple times within the same SaveChanges call if there are multiple emails
174-
public Task AfterSave(ITriggerContext<Email> context, CancellationToken cancellationToken) {
175-
if (context.ChangeType == ChangeType.Added) {
174+
public Task AfterSave(ITriggerContext<Email> context, CancellationToken cancellationToken) {
175+
if (context.ChangeType == ChangeType.Added) {
176176
this._emailAddresses.Add(context.Address);
177177
return Task.CompletedTask;
178178
}
179179
}
180-
180+
181181
public Task AfterSaveCompleted(CancellationToken cancellationToken) {
182182
Console.WriteLine($"We've sent {_emailAddresses.Count()} emails to {_emailAddresses.Distinct().Count()}" distinct email addresses");
183183
return Task.CompletedTask;
@@ -186,7 +186,7 @@ public BulkReportTrigger : IAfterSaveTrigger<Email>, IAfterSaveCompletedTrigger
186186
```
187187

188188
### Transactions
189-
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](https://docs.microsoft.com/en-us/ef/core/saving/transactions). Triggers are extensible and one such extension are [Transactional Triggers](https://www.nuget.org/packages/EntityFrameworkCore.Triggered.Transactions/). In order to use this plugin you will have to implement a few steps:
189+
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](https://docs.microsoft.com/en-us/ef/core/saving/transactions). Triggers are extensible and one such extension are [Transactional Triggers](https://www.nuget.org/packages/AutoGuru.EntityFrameworkCore.Triggered.Transactions/). In order to use this plugin you will have to implement a few steps:
190190
```csharp
191191
// OPTIONAL: Enable transactions when configuring triggers (Required ONLY when not using dependency injection)
192192
triggerOptions.UseTransactionTriggers();
@@ -197,25 +197,28 @@ var triggerSession = triggerService.CreateSession(context); // A trigger session
197197
198198
try {
199199
await context.SaveChangesAsync();
200-
await triggerSession.RaiseBeforeCommitTriggers();
200+
await triggerSession.RaiseBeforeCommitTriggers();
201201
await context.CommitAsync();
202202
await triggerSession.RaiseAfterCommitTriggers();
203203
}
204204
catch {
205205
await triggerSession.RaiseBeforeRollbackTriggers();
206206
await context.RollbackAsync();
207-
await triggerSession.RaiseAfterRollbackTriggers();
207+
await triggerSession.RaiseAfterRollbackTriggers();
208208
throw;
209209
}
210210
```
211211
In this example we were not able to inherit from TriggeredDbContext since we want to manually control the TriggerSession
212212

213213
### Custom trigger types
214-
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](https://github.com/koenbeuk/EntityFrameworkCore.Triggered/tree/master/src/EntityFrameworkCore.Triggered.Transactions) are implemented as an example.
214+
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](https://github.com/autoguru-au/EntityFrameworkCore.Triggered/tree/master/src/EntityFrameworkCore.Triggered.Transactions) are implemented as an example.
215215

216216
### Async triggers
217-
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](https://github.com/koenbeuk/EntityFrameworkCore.Triggered/issues/127). 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.
217+
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](https://github.com/autoguru-au/EntityFrameworkCore.Triggered/issues/127). 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.
218218
219219
### Similar products
220220
- [Ramses](https://github.com/JValck/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.
221221
- [EntityFramework.Triggers](https://github.com/NickStrupat/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.
222+
223+
## Acknowledgements
224+
This project is a fork of [EntityFrameworkCore.Triggered](https://github.com/koenbeuk/EntityFrameworkCore.Triggered) originally created by [koenbeuk](https://github.com/koenbeuk). Thank you for the excellent foundation.

0 commit comments

Comments
 (0)