https://github.com/win7user10/laraue.efcoretriggers
Library to write triggers in C# with EF.Core
https://github.com/win7user10/laraue.efcoretriggers
csharp database efcore orm triggers writing-triggers
Last synced: 10 months ago
JSON representation
Library to write triggers in C# with EF.Core
- Host: GitHub
- URL: https://github.com/win7user10/laraue.efcoretriggers
- Owner: win7user10
- License: mit
- Created: 2020-11-02T08:01:27.000Z (over 5 years ago)
- Default Branch: master
- Last Pushed: 2024-11-20T18:50:36.000Z (over 1 year ago)
- Last Synced: 2025-07-26T22:53:03.819Z (11 months ago)
- Topics: csharp, database, efcore, orm, triggers, writing-triggers
- Language: C#
- Homepage:
- Size: 675 KB
- Stars: 123
- Watchers: 6
- Forks: 22
- Open Issues: 26
-
Metadata Files:
- Readme: readme.md
- License: LICENSE
Awesome Lists containing this project
README
# Entity Framework Core Triggers
EfCoreTriggers is the library to write native SQL triggers using EFCore model builder. Triggers are automatically translating into sql and adding to migrations.
[](https://www.nuget.org/packages/Laraue.EfCoreTriggers.Common)
[](https://www.nuget.org/packages/Laraue.EfCoreTriggers.Common)
#### Versions compatability
| Package version | .NET version | EF Core version |
|-----------------|------------------|-----------------|
| 9.x.x | NET 9.0 | 9 |
| 8.x.x | NET 8.0 | 8 |
| 7.x.x | NET 6.0 | 7 |
| 5.x.x | NET standard 2.1 | 5 |
Install the provider package corresponding to your target database.
```sh
dotnet add package Laraue.EfCoreTriggers.PostgreSql
dotnet add package Laraue.EfCoreTriggers.MySql
dotnet add package Laraue.EfCoreTriggers.SqlServer
dotnet add package Laraue.EfCoreTriggers.SqlLite
```
### Basic usage
The library has extensions for EntityBuilder to configure DbContext.
After update Transaction entity, update records in the table with UserBalance entities.
```cs
modelBuilder.Entity()
.AfterUpdate(trigger => trigger
.Action(action => action
.Condition(tableRefs => tableRefs.Old.IsVeryfied && tableRefs.New.IsVeryfied) // Executes only if condition met
.Update(
(tableRefs, userBalances) => userBalances.UserId == tableRefs.Old.UserId, // Will be updated entities with matched condition
(tableRefs, oldBalance) => new UserBalance { Balance = oldBalance.Balance + tableRefs.New.Value - tableRefs.Old.Value }))); // New values for matched entities.
```
After insert Transaction entity, upsert record in the table with UserBalance entities.
```cs
modelBuilder.Entity()
.AfterDelete(trigger => trigger
.Action(action => action
.Condition(tableRefs => tableRefs.Old.IsVeryfied)
.Upsert(
(tableRefs, balances) => tableRefs.Old.UserId == balances.UserId, // If this field will match more than 0 rows, will be executed update operation for these rows else insert
tableRefs => new UserBalance { UserId = tableRefs.Old.UserId, Balance = tableRefs.Old.Value }, // Insert, if value didn't exist
(tableRefs, oldUserBalance) => new UserBalance { Balance = oldUserBalance.Balance + tableRefs.Old.Value }))); // Update all matched values
```
After delete Transaction entity, execute raw SQL. Pass deleted entity fields as arguments.
```cs
modelBuilder.Entity()
.AfterDelete(trigger => trigger
.Action(action => action
.ExecuteRawSql("PERFORM recalc_balance({0}, {1})"), tableRefs => tableRefs.Old.UserId, tableRefs => tableRefs.Old.Amount)));
```
Also, different trigger functions can be used to generate the SQL.
```
TriggerFunctions.GetTableName();
TriggerFunctions.GetColumnName(transaction => transaction.Value);
```
### All available triggers
| Trigger | PostgreSql | SQL Server | SQLite | MySQL |
| --- | --- | --- | --- | --- |
| Before Insert | + | - | + | + |
| After Insert | + | + | + | + |
| Instead Of Insert | + | + | + | - |
| Before Update | + | - | + | + |
| After Update | + | + | + | + |
| Instead Of Update | + | + | + | - |
| Before Delete | + | - | + | + |
| After Delete | + | + | + | + |
| Instead Of Delete | + | + | + | - |
### Available actions after trigger has worked
- Insert
- InsertIfNotExists
- Update
- Upsert
- Delete
- ExecuteRawSql
## Laraue.EfCoreTriggers.PostgreSql
[](https://www.nuget.org/packages/Laraue.EfCoreTriggers.PostgreSql)
[](https://www.nuget.org/packages/Laraue.EfCoreTriggers.PostgreSql)
### Basic usage
```cs
var options = new DbContextOptionsBuilder()
.UseNpgsql("User ID=test;Password=test;Host=localhost;Port=5432;Database=test;")
.UsePostgreSqlTriggers()
.Options;
var dbContext = new TestDbContext(options);
```
## Laraue.EfCoreTriggers.MySql
[](https://www.nuget.org/packages/Laraue.EfCoreTriggers.MySql)
[](https://www.nuget.org/packages/Laraue.EfCoreTriggers.MySql)
### Basic usage
```cs
var options = new DbContextOptionsBuilder()
.UseMySql("server=localhost;user=test;password=test;database=test;", new MySqlServerVersion(new Version(8, 0, 22))))
.UseMySqlTriggers()
.Options;
var dbContext = new TestDbContext(options);
```
## Laraue.EfCoreTriggers.SqlServer
[](https://www.nuget.org/packages/Laraue.EfCoreTriggers.SqlServer)
[](https://www.nuget.org/packages/Laraue.EfCoreTriggers.SqlServer)
### Basic usage
```cs
var options = new DbContextOptionsBuilder()
.UseSqlServer("Data Source=(LocalDb)\\v15.0;Database=test;Integrated Security=SSPI;")
.UseSqlServerTriggers()
.Options;
var dbContext = new TestDbContext(options);
```
## Laraue.EfCoreTriggers.SqlLite
[](https://www.nuget.org/packages/Laraue.EfCoreTriggers.SqlLite)
[](https://www.nuget.org/packages/Laraue.EfCoreTriggers.SqlLite)
### Basic usage
```cs
var options = new DbContextOptionsBuilder()
.UseSqlite("Filename=D://test.db")
.UseSqlLiteTriggers()
.Options;
var dbContext = new TestDbContext(options);
```
## Customization
Any service using for generation SQL can be replaced.
```cs
private class CustomDbSchemaRetriever : EfCoreDbSchemaRetriever
{
public CustomDbSchemaRetriever(IModel model) : base(model)
{
}
protected override string GetColumnName(Type type, MemberInfo memberInfo)
{
// Change strategy of naming some column
return 'c_' + base.GetColumnName(type, memberInfo);
}
}
```
Adding this service to the container
```cs
var options = new DbContextOptionsBuilder()
.UseNpgsql("User ID=test;Password=test;Host=localhost;Port=5432;Database=test;")
.UsePostgreSqlTriggers(services => services.AddSingleton)
.Options;
var dbContext = new TestDbContext(options);
```
### Adding translation of some custom function into sql code
To do this thing a custom function converter should be added to a provider
Let's image that we have an extension like
```cs
public static class StringExtensions
{
public static bool Like(this string str, string pattern)
{
throw new InvalidOperationException();
}
}
```
Now a custom converter should be written to translate this function into SQL
```cs
public abstract class StringExtensionsLikeConverter : MethodCallConverter
{
public override bool IsApplicable(MethodCallExpression expression)
{
return expression.Method.ReflectedType == typeof(StringExtensions) && MethodName == nameof(StringExtensions.Like);
}
public override SqlBuilder BuildSql(BaseExpressionProvider provider, MethodCallExpression expression)
{
// Generate SQL for arguments, they can be SQL expressions
var argumentSql = provider.GetMethodCallArgumentsSql(expression)[0];
// Generate SQL for this context, it also can be a SQL expression
var sqlBuilder = provider.GetExpressionSql(expression.Object);
// Combine SQL for object and SQL for arguments
// Output will be like "thisValueSql LIKE 'passedArgumentValueSql'"
return new(sqlBuilder.AffectedColumns, $"{sqlBuilder} LIKE {argumentSql}");
}
}
```
All custom converters should be added while setup a database
```cs
var options = new DbContextOptionsBuilder()
.UseSqlite("Filename=D://test.db")
.UseSqlLiteTriggers(services => services.AddMethodCallConverter(converter))
.Options;
var dbContext = new TestDbContext(options);
```
Now this function can be used in a trigger and it will be translated into SQL
```cs
modelBuilder.Entity()
.AfterDelete(trigger => trigger
.Action(action => action
.Condition(oldTransaction => oldTransaction.Description.Like('%payment%'))
```
### Trigger prefix customization
You can change the standard library prefix for trigger using the next static variable
```cs
Laraue.EfCoreTriggers.Common.Constants.AnnotationKey = "MY_PREFIX"
```
### Generic triggers
Define the generic trigger class inherits `GenericTrigger`
```cs
// Trigger for all classes inherits class Notification
private sealed class NotificationsTriggers : GenericTrigger
{
public override void ApplyTrigger(EntityTypeBuilder modelBuilder)
{
modelBuilder
.AfterInsert(x => x
.Action(y => y
.Insert(inserted => new NotificationLog
{
Text = inserted.New.Text,
NotificationType = typeof(TImplEntity).ToString(),
OriginalId = inserted.New.Id
})));
}
}
```
Registering generic trigger
```csharp
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.AddGenericTrigger(new NotificationsTriggers());
}
```