{"id":13431276,"url":"https://github.com/NickStrupat/EntityFramework.Triggers","last_synced_at":"2025-03-16T11:31:23.449Z","repository":{"id":15545222,"uuid":"18280157","full_name":"NickStrupat/EntityFramework.Triggers","owner":"NickStrupat","description":"Adds events for entity inserting, inserted, updating, updated, deleting, and deleted","archived":false,"fork":false,"pushed_at":"2025-01-05T20:06:15.000Z","size":981,"stargazers_count":378,"open_issues_count":8,"forks_count":46,"subscribers_count":27,"default_branch":"master","last_synced_at":"2025-03-09T09:03:03.676Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/NickStrupat.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2014-03-31T02:57:56.000Z","updated_at":"2025-01-21T02:03:30.000Z","dependencies_parsed_at":"2024-06-19T05:18:25.583Z","dependency_job_id":"9ca3b445-9cc2-447e-bbc3-c906e22b907d","html_url":"https://github.com/NickStrupat/EntityFramework.Triggers","commit_stats":{"total_commits":266,"total_committers":4,"mean_commits":66.5,"dds":0.05639097744360899,"last_synced_commit":"3a6d9c003a547f096e272b28714f7c29874f7323"},"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/NickStrupat%2FEntityFramework.Triggers","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/NickStrupat%2FEntityFramework.Triggers/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/NickStrupat%2FEntityFramework.Triggers/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/NickStrupat%2FEntityFramework.Triggers/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/NickStrupat","download_url":"https://codeload.github.com/NickStrupat/EntityFramework.Triggers/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243862835,"owners_count":20360221,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-07-31T02:01:01.874Z","updated_at":"2025-03-16T11:31:23.076Z","avatar_url":"https://github.com/NickStrupat.png","language":"C#","funding_links":[],"categories":["Frameworks, Libraries and Tools","Supported Packages","C#","框架, 库和工具","C\\#","ORM"],"sub_categories":["ORM","对象关系映射ORM","Contents"],"readme":"EntityFramework.Triggers\r\n========================\r\n\r\nAdd triggers to your entities with insert, update, and delete events. There are three events for each: before, after, and upon failure.\r\n\r\nThis repo contains the code for both the `EntityFramework` and `EntityFrameworkCore` projects, as well as the ASP.NET Core support projects.\r\n\r\n### Nuget packages for triggers\r\n\r\n| EF version  | .NET support                                    | NuGet package                                                                                                                                              |\r\n|:------------|:------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------|\r\n| \u003e= 6.1.3    | \u003e= Framework 4.6.1                              | [![NuGet Status](http://img.shields.io/nuget/v/EntityFramework.Triggers.svg?style=flat)](https://www.nuget.org/packages/EntityFramework.Triggers/)         |\r\n| \u003e= Core 2.0 | \u003e= Framework 4.6.1 \u0026#124;\u0026#124; \u003e= Standard 2.0 | [![NuGet Status](http://img.shields.io/nuget/v/EntityFrameworkCore.Triggers.svg?style=flat)](https://www.nuget.org/packages/EntityFrameworkCore.Triggers/) |\r\n\r\n### Nuget packages for ASP.NET Core dependency injection methods\r\n\r\n| EF version  | .NET support                                    | NuGet package                                                                                                                                                            |\r\n|:------------|:------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\r\n| \u003e= 6.1.3    | \u003e= Framework 4.6.1                              | [![NuGet Status](http://img.shields.io/nuget/v/NickStrupat.EntityFramework.Triggers.AspNetCore.svg?style=flat)](https://www.nuget.org/packages/NickStrupat.EntityFramework.Triggers.AspNetCore/)|\r\n| \u003e= Core 2.0 | \u003e= Framework 4.6.1 \u0026#124;\u0026#124; \u003e= Standard 2.0 | [![NuGet Status](http://img.shields.io/nuget/v/NickStrupat.EntityFrameworkCore.Triggers.AspNetCore.svg?style=flat)](https://www.nuget.org/packages/NickStrupat.EntityFrameworkCore.Triggers.AspNetCore/)               |\r\n\r\n## Basic usage with a global singleton\r\n\r\nTo use triggers on your entities, simply have your DbContext inherit from `DbContextWithTriggers`. If you can't change your DbContext inheritance chain, you simply need to override your `SaveChanges...` as demonstrated [below](#manual-overriding-to-enable-triggers)\r\n\r\n```csharp\r\npublic abstract class Trackable {\r\n\tpublic DateTime Inserted { get; private set; }\r\n\tpublic DateTime Updated { get; private set; }\r\n\r\n\tstatic Trackable() {\r\n\t\tTriggers\u003cTrackable\u003e.Inserting += entry =\u003e entry.Entity.Inserted = entry.Entity.Updated = DateTime.UtcNow;\r\n\t\tTriggers\u003cTrackable\u003e.Updating += entry =\u003e entry.Entity.Updated = DateTime.UtcNow;\r\n\t}\r\n}\r\n\r\npublic class Person : Trackable {\r\n\tpublic Int64 Id { get; private set; }\r\n\tpublic String Name { get; set; }\r\n}\r\n\r\npublic class Context : DbContextWithTriggers {\r\n\tpublic DbSet\u003cPerson\u003e People { get; set; }\r\n}\r\n```\r\n\r\nAs you may have guessed, what we're doing above is enabling automatic insert and update stamps for any entity that inherits `Trackable`. Events are raised from the base class/interfaces, up to the events specified on the entity class being used. It's just as easy to set up soft deletes (the Deleting, Updating, and Inserting events are cancellable from within a handler, logging, auditing, and more!).\r\n\r\n## Usage with dependency injection\r\n\r\nThis library fully supports dependency injection. The two features are:\r\n\r\n1) Injecting the triggers and handler registrations to avoid the global singleton in previous versions\r\n\r\n```csharp\r\nserviceCollection\r\n\t.AddSingleton(typeof(ITriggers\u003c,\u003e), typeof(Triggers\u003c,\u003e))\r\n\t.AddSingleton(typeof(ITriggers\u003c\u003e), typeof(Triggers\u003c\u003e))\r\n\t.AddSingleton(typeof(ITriggers), typeof(Triggers));\r\n```\r\n\r\n2) Using injected services right inside your global handlers\r\n\r\n```csharp\r\nTriggers\u003cPerson, Context\u003e().GlobalInserted.Add\u003cIServiceBus\u003e(\r\n\tentry =\u003e entry.Service.Broadcast(\"Inserted\", entry.Entity)\r\n);\r\n\r\nTriggers\u003cPerson, Context\u003e().GlobalInserted.Add\u003c(IServiceBus Bus, IServiceX X)\u003e(\r\n\tentry =\u003e {\r\n\t\tentry.Service.Bus.Broadcast(\"Inserted\", entry.Entity);\r\n\t\tentry.Service.X.DoSomething();\r\n\t}\r\n);\r\n```\r\n\r\n3) Using injected services right inside your injected handlers\r\n\r\n```csharp\r\npublic class Startup\r\n{\r\n\tpublic void ConfigureServices(IServiceCollection services)\r\n\t{\r\n\t\t...\r\n\t\tservices.AddDbContext\u003cContext\u003e();\r\n\t\tservices.AddTriggers();\r\n\t}\r\n\r\n\tpublic void Configure(IApplicationBuilder app, IHostingEnvironment env)\r\n\t{\r\n\t\t...\r\n\t\tapp.UseTriggers(builder =\u003e\r\n\t\t{\r\n\t\t\tbuilder.Triggers().Inserted.Add(\r\n\t\t\t\tentry =\u003e Debug.WriteLine(entry.Entity.ToString())\r\n\t\t\t);\r\n\t\t\tbuilder.Triggers\u003cPerson, Context\u003e().Inserted.Add(\r\n\t\t\t\tentry =\u003e Debug.WriteLine(entry.Entity.FirstName)\r\n\t\t\t);\r\n\r\n\t\t\t// receive injected services inside your handler, either with just a single service type or with a value tuple of services\r\n\t\t\tbuilder.Triggers\u003cPerson, Context\u003e().GlobalInserted.Add\u003cIServiceBus\u003e(\r\n\t\t\t\tentry =\u003e entry.Service.Broadcast(\"Inserted\", entry.Entity)\r\n\t\t\t);\r\n\t\t\tbuilder.Triggers\u003cPerson, Context\u003e().GlobalInserted.Add\u003c(IServiceBus Bus, IServiceX X)\u003e(\r\n\t\t\t\tentry =\u003e {\r\n\t\t\t\t\tentry.Service.Bus.Broadcast(\"Inserted\", entry.Entity);\r\n\t\t\t\t\tentry.Service.X.DoSomething();\r\n\t\t\t\t}\r\n\t\t\t);\r\n\t\t});\r\n\t}\r\n}\r\n```\r\n\r\n## How to enable triggers if you can't derive from `DbContextWithTriggers`\r\n\r\nIf you can't easily change what your `DbContext` class inherits from (ASP.NET Identity users, for example), you can override your `SaveChanges...` methods to call the `SaveChangesWithTriggers...` extension methods. Alternatively, you can call `SaveChangesWithTriggers...` directly instead of `SaveChanges...` if, for example, you want to control which changes cause triggers to be fired.\r\n\r\n```csharp\r\nclass YourContext : DbContext {\r\n\t// Your usual DbSet\u003c\u003e properties\r\n\r\n\t#region If you're targeting EF 6\r\n\tpublic override Int32 SaveChanges() {\r\n\t\treturn this.SaveChangesWithTriggers(base.SaveChanges);\r\n\t}\r\n\tpublic override Task\u003cInt32\u003e SaveChangesAsync(CancellationToken cancellationToken) {\r\n\t\treturn this.SaveChangesWithTriggersAsync(base.SaveChangesAsync, cancellationToken);\r\n\t}\r\n\t#endregion\r\n\r\n\t#region If you're targeting EF Core\r\n\tpublic override Int32 SaveChanges() {\r\n\t\treturn this.SaveChangesWithTriggers(base.SaveChanges, acceptAllChangesOnSuccess: true);\r\n\t}\r\n\tpublic override Int32 SaveChanges(Boolean acceptAllChangesOnSuccess) {\r\n\t\treturn this.SaveChangesWithTriggers(base.SaveChanges, acceptAllChangesOnSuccess);\r\n\t}\r\n\tpublic override Task\u003cInt32\u003e SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken)) {\r\n\t\treturn this.SaveChangesWithTriggersAsync(base.SaveChangesAsync, acceptAllChangesOnSuccess: true, cancellationToken: cancellationToken);\r\n\t}\r\n\tpublic override Task\u003cInt32\u003e SaveChangesAsync(Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken = default(CancellationToken)) {\r\n\t\treturn this.SaveChangesWithTriggersAsync(base.SaveChangesAsync, acceptAllChangesOnSuccess, cancellationToken);\r\n\t}\r\n\t#endregion\r\n}\r\n\r\n#region If you didn't/can't override `SaveChanges...`, you can (not recommended) call \r\ndbContext.SaveChangesWithTriggers(dbContext.SaveChanges);\r\ndbContext.SaveChangesWithTriggersAsync(dbContext.SaveChangesAsync);\r\n#endregion\r\n```\r\n\r\n## Longer example (targeting EF6 for now)\r\n\r\n```csharp\r\nusing System;\r\nusing System.Data.Entity;\r\nusing System.Data.Entity.Infrastructure;\r\nusing System.Data.Entity.Migrations;\r\nusing System.Linq;\r\nusing System.Threading;\r\nusing System.Threading.Tasks;\r\nusing EntityFramework.Triggers;\r\n\r\nnamespace Example {\r\n\tpublic class Program {\r\n\t\tpublic abstract class Trackable {\r\n\t\t\tpublic virtual DateTime Inserted { get; private set; }\r\n\t\t\tpublic virtual DateTime Updated { get; private set; }\r\n\r\n\t\t\tstatic Trackable() {\r\n\t\t\t\tTriggers\u003cTrackable\u003e.Inserting += entry =\u003e entry.Entity.Inserted = entry.Entity.Updated = DateTime.UtcNow;\r\n\t\t\t\tTriggers\u003cTrackable\u003e.Updating += entry =\u003e entry.Entity.Updated = DateTime.UtcNow;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tpublic abstract class SoftDeletable : Trackable {\r\n\t\t\tpublic virtual DateTime? Deleted { get; private set; }\r\n\r\n\t\t\tpublic Boolean IsSoftDeleted =\u003e Deleted != null;\r\n\t\t\tpublic void SoftDelete() =\u003e Deleted = DateTime.UtcNow;\r\n\t\t\tpublic void SoftRestore() =\u003e Deleted = null;\r\n\r\n\t\t\tstatic SoftDeletable() {\r\n\t\t\t\tTriggers\u003cSoftDeletable\u003e.Deleting += entry =\u003e {\r\n\t\t\t\t\tentry.Entity.SoftDelete();\r\n\t\t\t\t\tentry.Cancel = true; // Cancels the deletion, but will persist changes with the same effects as EntityState.Modified\r\n\t\t\t\t};\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tpublic class Person : SoftDeletable {\r\n\t\t\tpublic virtual Int64 Id { get; private set; }\r\n\t\t\tpublic virtual String FirstName { get; set; }\r\n\t\t\tpublic virtual String LastName { get; set; }\r\n\t\t}\r\n\r\n\t\tpublic class LogEntry {\r\n\t\t\tpublic virtual Int64 Id { get; private set; }\r\n\t\t\tpublic virtual String Message { get; set; }\r\n\t\t}\r\n\r\n\t\tpublic class Context : DbContextWithTriggers {\r\n\t\t\tpublic virtual DbSet\u003cPerson\u003e People { get; set; }\r\n\t\t\tpublic virtual DbSet\u003cLogEntry\u003e Log { get; set; }\r\n\t\t}\r\n\t\tinternal sealed class Configuration : DbMigrationsConfiguration\u003cContext\u003e {\r\n\t\t\tpublic Configuration() {\r\n\t\t\t\tAutomaticMigrationsEnabled = true;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tstatic Program() {\r\n\t\t\tTriggers\u003cPerson, Context\u003e.Inserting += e =\u003e {\r\n\t\t\t\te.Context.Log.Add(new LogEntry { Message = \"Insert trigger fired for \" + e.Entity.FirstName });\r\n\t\t\t\tConsole.WriteLine(\"Inserting \" + e.Entity.FirstName);\r\n\t\t\t};\r\n\t\t\tTriggers\u003cPerson\u003e.Updating += e =\u003e Console.WriteLine($\"Updating {e.Original.FirstName} to {e.Entity.FirstName}\");\r\n\t\t\tTriggers\u003cPerson\u003e.Deleting += e =\u003e Console.WriteLine(\"Deleting \" + e.Entity.FirstName);\r\n\t\t\tTriggers\u003cPerson\u003e.Inserted += e =\u003e Console.WriteLine(\"Inserted \" + e.Entity.FirstName);\r\n\t\t\tTriggers\u003cPerson\u003e.Updated += e =\u003e Console.WriteLine(\"Updated \" + e.Entity.FirstName);\r\n\t\t\tTriggers\u003cPerson\u003e.Deleted += e =\u003e Console.WriteLine(\"Deleted \" + e.Entity.FirstName);\r\n\t\t}\r\n\t\t\r\n\t\tprivate static void Main(String[] args) =\u003e Task.WaitAll(MainAsync(args));\r\n\r\n\t\tprivate static async Task MainAsync(String[] args) {\r\n\t\t\tusing (var context = new Context()) {\r\n\t\t\t\tcontext.Database.Delete();\r\n\t\t\t\tcontext.Database.Create();\r\n\r\n\t\t\t\tvar log = context.Log.ToList();\r\n\t\t\t\tvar nickStrupat = new Person {\r\n\t\t\t\t\tFirstName = \"Nick\",\r\n\t\t\t\t\tLastName = \"Strupat\"\r\n\t\t\t\t};\r\n\r\n\t\t\t\tcontext.People.Add(nickStrupat);\r\n\t\t\t\tawait context.SaveChangesAsync();\r\n\r\n\t\t\t\tnickStrupat.FirstName = \"Nicholas\";\r\n\t\t\t\tcontext.SaveChanges();\r\n\t\t\t\tcontext.People.Remove(nickStrupat);\r\n\t\t\t\tawait context.SaveChangesAsync();\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}\r\n```\r\n\r\n## See also\r\n\r\n- [https://github.com/NickStrupat/EntityFramework.Rx](https://github.com/NickStrupat/EntityFramework.Rx) for **hot** observables of your EF operations\r\n- [https://github.com/NickStrupat/EntityFramework.PrimaryKey](https://github.com/NickStrupat/EntityFramework.PrimaryKey) to easily get the primary key of any entity (including composite keys)\r\n- [https://github.com/NickStrupat/EntityFramework.TypedOriginalValues](https://github.com/NickStrupat/EntityFramework.TypedOriginalValues) to get a proxy object of the orginal values of your entity (typed access to Property(\"...\").OriginalValue)\r\n- [https://github.com/NickStrupat/EntityFramework.SoftDeletable](https://github.com/NickStrupat/EntityFramework.SoftDeletable) for base classes which encapsulate the soft-delete pattern (including keeping a history with user id, etc.)\r\n- [https://github.com/NickStrupat/EntityFramework.VersionedProperties](https://github.com/NickStrupat/EntityFramework.VersionedProperties) for a library of classes which auto-magically keep an audit history of the changes to the specified property\r\n\r\n## Contributing\r\n\r\n1. [Create an issue](https://github.com/NickStrupat/EntityFramework.Triggers/issues/new)\r\n2. Let's find some point of agreement on your suggestion.\r\n3. Fork it!\r\n4. Create your feature branch: `git checkout -b my-new-feature`\r\n5. Commit your changes: `git commit -am 'Add some feature'`\r\n6. Push to the branch: `git push origin my-new-feature`\r\n7. Submit a pull request :D\r\n\r\n## History\r\n\r\n[Commit history](https://github.com/NickStrupat/EntityFramework.Triggers/commits/master)\r\n\r\n## License\r\n\r\n[MIT License](https://github.com/NickStrupat/EntityFramework.Triggers/blob/master/README.md)\r\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FNickStrupat%2FEntityFramework.Triggers","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FNickStrupat%2FEntityFramework.Triggers","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FNickStrupat%2FEntityFramework.Triggers/lists"}