• Stars
    star
    367
  • Rank 116,257 (Top 3 %)
  • Language
    C#
  • License
    MIT License
  • Created over 10 years ago
  • Updated over 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Adds events for entity inserting, inserted, updating, updated, deleting, and deleted

EntityFramework.Triggers

Add triggers to your entities with insert, update, and delete events. There are three events for each: before, after, and upon failure.

This repo contains the code for both the EntityFramework and EntityFrameworkCore projects, as well as the ASP.NET Core support projects.

Nuget packages for triggers

EF version .NET support NuGet package
>= 6.1.3 >= Framework 4.6.1 NuGet Status
>= Core 2.0 >= Framework 4.6.1 || >= Standard 2.0 NuGet Status

Nuget packages for ASP.NET Core dependency injection methods

EF version .NET support NuGet package
>= 6.1.3 >= Framework 4.6.1 NuGet Status
>= Core 2.0 >= Framework 4.6.1 || >= Standard 2.0 NuGet Status

Basic usage with a global singleton

To 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

public abstract class Trackable {
	public DateTime Inserted { get; private set; }
	public DateTime Updated { get; private set; }

	static Trackable() {
		Triggers<Trackable>.Inserting += entry => entry.Entity.Inserted = entry.Entity.Updated = DateTime.UtcNow;
		Triggers<Trackable>.Updating += entry => entry.Entity.Updated = DateTime.UtcNow;
	}
}

public class Person : Trackable {
	public Int64 Id { get; private set; }
	public String Name { get; set; }
}

public class Context : DbContextWithTriggers {
	public DbSet<Person> People { get; set; }
}

As 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!).

Usage with dependency injection

This library fully supports dependency injection. The two features are:

  1. Injecting the triggers and handler registrations to avoid the global singleton in previous versions
serviceCollection
	.AddSingleton(typeof(ITriggers<,>), typeof(Triggers<,>))
	.AddSingleton(typeof(ITriggers<>), typeof(Triggers<>))
	.AddSingleton(typeof(ITriggers), typeof(Triggers));
  1. Using injected services right inside your global handlers
Triggers<Person, Context>().GlobalInserted.Add<IServiceBus>(
	entry => entry.Service.Broadcast("Inserted", entry.Entity)
);

Triggers<Person, Context>().GlobalInserted.Add<(IServiceBus Bus, IServiceX X)>(
	entry => {
		entry.Service.Bus.Broadcast("Inserted", entry.Entity);
		entry.Service.X.DoSomething();
	}
);
  1. Using injected services right inside your injected handlers
public class Startup
{
	public void ConfigureServices(IServiceCollection services)
	{
		...
		services.AddDbContext<Context>();
		services.AddTriggers();
	}

	public void Configure(IApplicationBuilder app, IHostingEnvironment env)
	{
		...
		app.UseTriggers(builder =>
		{
			builder.Triggers().Inserted.Add(
				entry => Debug.WriteLine(entry.Entity.ToString())
			);
			builder.Triggers<Person, Context>().Inserted.Add(
				entry => Debug.WriteLine(entry.Entity.FirstName)
			);

			// receive injected services inside your handler, either with just a single service type or with a value tuple of services
			builder.Triggers<Person, Context>().GlobalInserted.Add<IServiceBus>(
				entry => entry.Service.Broadcast("Inserted", entry.Entity)
			);
			builder.Triggers<Person, Context>().GlobalInserted.Add<(IServiceBus Bus, IServiceX X)>(
				entry => {
					entry.Service.Bus.Broadcast("Inserted", entry.Entity);
					entry.Service.X.DoSomething();
				}
			);
		});
	}
}

How to enable triggers if you can't derive from DbContextWithTriggers

If 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.

class YourContext : DbContext {
	// Your usual DbSet<> properties

	#region If you're targeting EF 6
	public override Int32 SaveChanges() {
		return this.SaveChangesWithTriggers(base.SaveChanges);
	}
	public override Task<Int32> SaveChangesAsync(CancellationToken cancellationToken) {
		return this.SaveChangesWithTriggersAsync(base.SaveChangesAsync, cancellationToken);
	}
	#endregion

	#region If you're targeting EF Core
	public override Int32 SaveChanges() {
		return this.SaveChangesWithTriggers(base.SaveChanges, acceptAllChangesOnSuccess: true);
	}
	public override Int32 SaveChanges(Boolean acceptAllChangesOnSuccess) {
		return this.SaveChangesWithTriggers(base.SaveChanges, acceptAllChangesOnSuccess);
	}
	public override Task<Int32> SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken)) {
		return this.SaveChangesWithTriggersAsync(base.SaveChangesAsync, acceptAllChangesOnSuccess: true, cancellationToken: cancellationToken);
	}
	public override Task<Int32> SaveChangesAsync(Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken = default(CancellationToken)) {
		return this.SaveChangesWithTriggersAsync(base.SaveChangesAsync, acceptAllChangesOnSuccess, cancellationToken);
	}
	#endregion
}

#region If you didn't/can't override `SaveChanges...`, you can (not recommended) call 
dbContext.SaveChangesWithTriggers(dbContext.SaveChanges);
dbContext.SaveChangesWithTriggersAsync(dbContext.SaveChangesAsync);
#endregion

Longer example (targeting EF6 for now)

using System;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Data.Entity.Migrations;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using EntityFramework.Triggers;

namespace Example {
	public class Program {
		public abstract class Trackable {
			public virtual DateTime Inserted { get; private set; }
			public virtual DateTime Updated { get; private set; }

			static Trackable() {
				Triggers<Trackable>.Inserting += entry => entry.Entity.Inserted = entry.Entity.Updated = DateTime.UtcNow;
				Triggers<Trackable>.Updating += entry => entry.Entity.Updated = DateTime.UtcNow;
			}
		}

		public abstract class SoftDeletable : Trackable {
			public virtual DateTime? Deleted { get; private set; }

			public Boolean IsSoftDeleted => Deleted != null;
			public void SoftDelete() => Deleted = DateTime.UtcNow;
			public void SoftRestore() => Deleted = null;

			static SoftDeletable() {
				Triggers<SoftDeletable>.Deleting += entry => {
					entry.Entity.SoftDelete();
					entry.Cancel = true; // Cancels the deletion, but will persist changes with the same effects as EntityState.Modified
				};
			}
		}

		public class Person : SoftDeletable {
			public virtual Int64 Id { get; private set; }
			public virtual String FirstName { get; set; }
			public virtual String LastName { get; set; }
		}

		public class LogEntry {
			public virtual Int64 Id { get; private set; }
			public virtual String Message { get; set; }
		}

		public class Context : DbContextWithTriggers {
			public virtual DbSet<Person> People { get; set; }
			public virtual DbSet<LogEntry> Log { get; set; }
		}
		internal sealed class Configuration : DbMigrationsConfiguration<Context> {
			public Configuration() {
				AutomaticMigrationsEnabled = true;
			}
		}

		static Program() {
			Triggers<Person, Context>.Inserting += e => {
				e.Context.Log.Add(new LogEntry { Message = "Insert trigger fired for " + e.Entity.FirstName });
				Console.WriteLine("Inserting " + e.Entity.FirstName);
			};
			Triggers<Person>.Updating += e => Console.WriteLine($"Updating {e.Original.FirstName} to {e.Entity.FirstName}");
			Triggers<Person>.Deleting += e => Console.WriteLine("Deleting " + e.Entity.FirstName);
			Triggers<Person>.Inserted += e => Console.WriteLine("Inserted " + e.Entity.FirstName);
			Triggers<Person>.Updated += e => Console.WriteLine("Updated " + e.Entity.FirstName);
			Triggers<Person>.Deleted += e => Console.WriteLine("Deleted " + e.Entity.FirstName);
		}
		
		private static void Main(String[] args) => Task.WaitAll(MainAsync(args));

		private static async Task MainAsync(String[] args) {
			using (var context = new Context()) {
				context.Database.Delete();
				context.Database.Create();

				var log = context.Log.ToList();
				var nickStrupat = new Person {
					FirstName = "Nick",
					LastName = "Strupat"
				};

				context.People.Add(nickStrupat);
				await context.SaveChangesAsync();

				nickStrupat.FirstName = "Nicholas";
				context.SaveChanges();
				context.People.Remove(nickStrupat);
				await context.SaveChangesAsync();
			}
		}
	}
}

See also

Contributing

  1. Create an issue
  2. Let's find some point of agreement on your suggestion.
  3. Fork it!
  4. Create your feature branch: git checkout -b my-new-feature
  5. Commit your changes: git commit -am 'Add some feature'
  6. Push to the branch: git push origin my-new-feature
  7. Submit a pull request :D

History

Commit history

License

MIT License

More Repositories

1

CacheLineSize

A cross-platform C function to get the cache line size (in bytes) of the processor, or 0 on failure
C
106
star
2

EntityFramework.PrimaryKey

Retrieve the primary key (including composite keys) from any entity
C#
44
star
3

EntityFramework.VersionedProperties

C#
40
star
4

Aligned

Memory alignment wrappers, useful for avoiding false sharing
C++
39
star
5

EntityFramework.TypedOriginalValues

Get typed access to the DbEntityEntry<T>.OriginalValues property bag
C#
33
star
6

PagedList.EntityFramework

An async/await enabled extension for https://github.com/kpi-ua/X.PagedList (previously https://github.com/TroyGoode/PagedList)
C#
20
star
7

TimeAgo

Get a string showing how long ago a DateTime was, for example '4 minutes ago' in many languages
C#
20
star
8

NameOf

Provides strongly typed access to a compile-time string representing the name of a variable, field, property, method, event, enum, or type.
C#
20
star
9

AlignedMalloc

A cross-platform C function to allocate aligned memory
C
18
star
10

ComputerInfo

.NET Standard library to fill the need for Microsoft.VisualBasic.Devices.ComputerInfo on Windows, macOS, and Linux
C#
17
star
11

CacheLineSize.NET

A cross-platform .NET Standard library to get the cache line size (in bytes) of the processor.
C#
13
star
12

BrainfuckSharpCompiler

A brainfuck compiler written in C#. Compiles to an executable .NET assembly
C#
8
star
13

MinimalPerfectHash

A minimal perfect hash function library derived from Laurent Dupuis' implementation at http://www.dupuis.me/node/9
C#
7
star
14

Equality

The last .NET equality solution you'll ever need. Automatically produces equality comparison and hash-code generation for any type by emitting IL based on your type. Emitted code is cached and specialized for struct and class types. Specify fields and auto-properties to ignore, as well as properties you want to include by applying attributes.
C#
6
star
15

CoContra

Delegate replacement and event backing-field drop-in classes to easily allow co/contra-variance in delegate
C#
5
star
16

Mutuple

Mutable tuple class library for those times when you need mutable, reference-type tuples
C#
5
star
17

EntityFramework.SoftDeletable

Soft-deletable base classes and helpers for EntityFramework
C#
3
star
18

TorontoBeachPredictor

C#
3
star
19

SudokuSolver

C#
2
star
20

Catch

LINQ extention method for catching exceptions
C#
2
star
21

SlimCollections

Some common collections implemented as a struct with the first ten items allocated within the struct. Ideal for situations requiring the semantics of a collection with little to no GC pressure.
C#
2
star
22

CacheAlignedMalloc

C
1
star
23

BrainfuckSharp

A brainfuck interpreter I wrote in C#
C#
1
star
24

NonNullable

NonNullable<T> struct with IL-weaving to prevent all possible null assignment (via default(T) and the parameter-less constructor)
C#
1
star
25

Disposal

C#
1
star
26

re2c-unicode-categories

Tool to generate Unicode category definitions for re2c lexers
C++
1
star
27

virtual_memory

Cross-platform virtual memory allocation functions: reserve, commit, decommit, and release
1
star
28

MarshalAligned

.NET aligned memory allocation methods in the style of `System.Runtime.InteropServices.Marshal`
C#
1
star
29

AlignedMemoryManager

Derived from `MemoryManager<T>` to support aligned `Memory<T>`
C#
1
star
30

SpscBoundedQueue

Concurrent, lock-free, single-producer, single-consumer, bounded queue
C++
1
star
31

Ephemera

Attach and use properties to objects at run-time
C#
1
star
32

DiscogsNet

A fork of https://discogsnet.codeplex.com
C#
1
star
33

Brainfuck

A brainfuck interpreter I wrote in C++
1
star
34

NichoblockStruchain

A hypertext machine learning web zone for blockchain AI hype trains and other cryptos/quanta (not affiliated with the BlocholasChainpat folk)
C#
1
star