Skip to content

Commit a4f0107

Browse files
committed
2 parents 2c12b95 + 1a478b2 commit a4f0107

File tree

14 files changed

+288
-65
lines changed

14 files changed

+288
-65
lines changed

benchmarks/EntityFrameworkCore.Triggered.Benchmarks/EmbracingFeaturesBenchmarks.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,10 @@ public void GlobalSetup()
2626
.AddTriggeredDbContext<TriggeredApplicationContext>(options => {
2727
options
2828
.UseInMemoryDatabase(nameof(WithTriggeredDbContext))
29-
.UseTriggers();
29+
.UseTriggers(triggerOptions => {
30+
triggerOptions.AddTrigger<Triggers.SetStudentRegistrationDateTrigger>();
31+
triggerOptions.AddTrigger<Triggers.SignStudentUpForMandatoryCourses>();
32+
});
3033
})
3134
.AddDbContext<ApplicationContextWithTriggers>(options => {
3235
options
@@ -36,8 +39,6 @@ public void GlobalSetup()
3639
options
3740
.UseInMemoryDatabase(nameof(RamsesApplicationContext));
3841
})
39-
.AddSingleton<IBeforeSaveTrigger<Student>, Triggers.SetStudentRegistrationDateTrigger>()
40-
.AddScoped<IBeforeSaveTrigger<Student>, Triggers.SignStudentUpForMandatoryCourses>()
4142
.AddTriggers()
4243
.AddSingleton(typeof(ITriggers<,>), typeof(Triggers<,>))
4344
.AddSingleton(typeof(ITriggers<>), typeof(Triggers<>))

benchmarks/EntityFrameworkCore.Triggered.Benchmarks/EntityFrameworkCore.Triggered.Benchmarks.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,10 @@
1313
</PropertyGroup>
1414
<ItemGroup>
1515
<PackageReference Include="BenchmarkDotNet" Version="0.12.1" />
16-
<PackageReference Include="EntityFrameworkCore.Triggered" Version="2.1.0" />
1716
<PackageReference Include="EntityFrameworkCore.Triggers" Version="1.2.2" />
17+
<PackageReference Include="EntityFrameworkCore.Triggered" Version="2.2.0" />
1818
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="5.0.0" />
1919
<PackageReference Include="Ramses" Version="3.0.1" />
2020
</ItemGroup>
21+
2122
</Project>
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Reflection;
5+
using System.Text;
6+
using System.Threading.Tasks;
7+
using EntityFrameworkCore.Triggered.Infrastructure;
8+
using Microsoft.Extensions.DependencyInjection;
9+
10+
namespace Microsoft.EntityFrameworkCore
11+
{
12+
public static class TriggersContextOptionsBuilderExtensions
13+
{
14+
public static TriggersContextOptionsBuilder AddAssemblyTriggers(this TriggersContextOptionsBuilder builder)
15+
=> AddAssemblyTriggers(builder, Assembly.GetCallingAssembly());
16+
17+
public static TriggersContextOptionsBuilder AddAssemblyTriggers(this TriggersContextOptionsBuilder builder, ServiceLifetime lifetime)
18+
=> AddAssemblyTriggers(builder, lifetime, Assembly.GetCallingAssembly());
19+
20+
public static TriggersContextOptionsBuilder AddAssemblyTriggers(this TriggersContextOptionsBuilder builder, params Assembly[] assemblies)
21+
=> AddAssemblyTriggers(builder, ServiceLifetime.Scoped, assemblies);
22+
23+
public static TriggersContextOptionsBuilder AddAssemblyTriggers(this TriggersContextOptionsBuilder builder, ServiceLifetime lifetime, params Assembly[] assemblies)
24+
{
25+
if (assemblies is null)
26+
{
27+
throw new ArgumentNullException(nameof(assemblies));
28+
}
29+
30+
var assemblyTypes = assemblies
31+
.SelectMany(x => x.GetTypes())
32+
.Where(x => x.IsClass);
33+
34+
foreach (var assemblyType in assemblyTypes)
35+
{
36+
builder.AddTrigger(assemblyType, lifetime);
37+
}
38+
39+
return builder;
40+
}
41+
42+
}
43+
}

src/EntityFrameworkCore.Triggered.Transactions/TriggeredSessionExtensions.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ public static async Task RaiseBeforeCommitStartingTriggers(this ITriggerSession
9191
throw new InvalidOperationException("Method is implemented for concrete TriggerSessions only");
9292
}
9393

94-
var triggers = typedTriggerSession.DiscoveryService.ServiceProvider.GetServices<IBeforeCommitStartingTrigger>();
94+
var triggers = typedTriggerSession.DiscoveryService.DiscoverTriggers<IBeforeCommitStartingTrigger>();
9595

9696
foreach (var trigger in triggers)
9797
{
@@ -111,7 +111,7 @@ public static async Task RaiseBeforeCommitCompletedTriggers(this ITriggerSession
111111
throw new InvalidOperationException("Method is implemented for concrete TriggerSessions only");
112112
}
113113

114-
var triggers = typedTriggerSession.DiscoveryService.ServiceProvider.GetServices<IBeforeCommitCompletedTrigger>();
114+
var triggers = typedTriggerSession.DiscoveryService.DiscoverTriggers<IBeforeCommitCompletedTrigger>();
115115

116116
foreach (var trigger in triggers)
117117
{
@@ -131,7 +131,7 @@ public static async Task RaiseAfterCommitStartingTriggers(this ITriggerSession t
131131
throw new InvalidOperationException("Method is implemented for concrete TriggerSessions only");
132132
}
133133

134-
var triggers = typedTriggerSession.DiscoveryService.ServiceProvider.GetServices<IAfterCommitStartingTrigger>();
134+
var triggers = typedTriggerSession.DiscoveryService.DiscoverTriggers<IAfterCommitStartingTrigger>();
135135

136136
foreach (var trigger in triggers)
137137
{
@@ -151,7 +151,7 @@ public static async Task RaiseAfterCommitCompletedTriggers(this ITriggerSession
151151
throw new InvalidOperationException("Method is implemented for concrete TriggerSessions only");
152152
}
153153

154-
var triggers = typedTriggerSession.DiscoveryService.ServiceProvider.GetServices<IAfterCommitCompletedTrigger>();
154+
var triggers = typedTriggerSession.DiscoveryService.DiscoverTriggers<IAfterCommitCompletedTrigger>();
155155

156156
foreach (var trigger in triggers)
157157
{

src/EntityFrameworkCore.Triggered/Infrastructure/Internal/TriggersOptionExtension.cs

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Linq;
4+
using System.Linq.Expressions;
45
using EntityFrameworkCore.Triggered.Internal;
56
using EntityFrameworkCore.Triggered.Internal.CascadeStrategies;
67
using EntityFrameworkCore.Triggered.Lifecycles;
@@ -147,6 +148,8 @@ public void ApplyServices(IServiceCollection services)
147148
services.AddScoped<IResettableService>(serviceProvider => serviceProvider.GetRequiredService<TriggerService>());
148149
services.TryAddScoped<ITriggerService>(serviceProvider => serviceProvider.GetRequiredService<TriggerService>());
149150

151+
services.AddTransient<TriggerFactory>();
152+
150153
#if EFCORETRIGGERED2
151154
services.TryAddScoped<IInterceptor, TriggerSessionSaveChangesInterceptor>();
152155
#endif
@@ -165,36 +168,38 @@ public void ApplyServices(IServiceCollection services)
165168

166169
services.TryAddTransient(typeof(ICascadeStrategy), cascadeStrategyType);
167170

168-
if (_triggers != null)
171+
if (_triggers != null && _triggerTypes != null)
169172
{
170173
foreach (var (typeOrInstance, lifetime) in _triggers)
171174
{
172-
var (triggerType, triggerInstance) = typeOrInstance switch
173-
{
175+
var (triggerServiceType, triggerServiceInstance) = typeOrInstance switch {
174176
Type type => (type, null),
175177
object instance => (instance.GetType(), instance),
176178
_ => throw new InvalidOperationException("Unknown type registration")
177179
};
178180

179-
if (_triggerTypes != null)
181+
var instanceParamExpression = Expression.Parameter(typeof(object), "object");
182+
183+
var triggerInstanceFactoryBuilder =
184+
Expression.Lambda<Func<object?, object>>(
185+
Expression.New(
186+
typeof(TriggerInstanceFactory<>).MakeGenericType(triggerServiceType).GetConstructor(new[] { typeof(object) }),
187+
instanceParamExpression
188+
),
189+
instanceParamExpression
190+
)
191+
.Compile();
192+
193+
foreach (var triggerType in _triggerTypes.Distinct())
180194
{
181-
foreach (var customTriggerType in _triggerTypes.Distinct())
195+
var triggerTypeImplementations = triggerType.IsGenericTypeDefinition
196+
? TypeHelpers.FindGenericInterfaces(triggerServiceType, triggerType)
197+
: triggerServiceType.GetInterfaces().Where(x => x == triggerType);
198+
199+
foreach (var triggerTypeImplementation in triggerTypeImplementations)
182200
{
183-
var customTriggers = customTriggerType.IsGenericTypeDefinition
184-
? TypeHelpers.FindGenericInterfaces(triggerType, customTriggerType)
185-
: triggerType.GetInterfaces().Where(x => x == customTriggerType);
186-
187-
foreach (var customTrigger in customTriggers)
188-
{
189-
if (triggerInstance != null)
190-
{
191-
services.Add(new ServiceDescriptor(customTrigger, triggerInstance));
192-
}
193-
else
194-
{
195-
services.Add(new ServiceDescriptor(customTrigger, triggerType, lifetime));
196-
}
197-
}
201+
var triggerTypeImplementationFactoryType = typeof(ITriggerInstanceFactory<>).MakeGenericType(triggerTypeImplementation);
202+
services.Add(new ServiceDescriptor(triggerTypeImplementationFactoryType, _ => triggerInstanceFactoryBuilder(triggerServiceInstance), lifetime));
198203
}
199204
}
200205
}

src/EntityFrameworkCore.Triggered/Internal/ITriggerDiscoveryService.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ namespace EntityFrameworkCore.Triggered.Internal
66
public interface ITriggerDiscoveryService
77
{
88
IEnumerable<TriggerDescriptor> DiscoverTriggers(Type openTriggerType, Type entityType, Func<Type, ITriggerTypeDescriptor> triggerTypeDescriptorFactory);
9-
IServiceProvider ServiceProvider { get; set; }
9+
10+
IEnumerable<TTrigger> DiscoverTriggers<TTrigger>();
11+
12+
public IServiceProvider ServiceProvider { get; set; }
1013
}
1114
}

src/EntityFrameworkCore.Triggered/Internal/TriggerDiscoveryService.cs

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,22 +11,21 @@ namespace EntityFrameworkCore.Triggered.Internal
1111
public sealed class TriggerDiscoveryService : ITriggerDiscoveryService, IResettableService
1212
{
1313
readonly static TriggerDescriptorComparer _triggerDescriptorComparer = new();
14-
1514
readonly ITriggerServiceProviderAccessor _triggerServiceProviderAccessor;
1615
readonly ITriggerTypeRegistryService _triggerTypeRegistryService;
16+
readonly TriggerFactory _triggerFactory;
1717

1818
IServiceProvider? _serviceProvider;
1919

20-
public TriggerDiscoveryService(ITriggerServiceProviderAccessor triggerServiceProviderAccessor, ITriggerTypeRegistryService triggerTypeRegistryService)
20+
public TriggerDiscoveryService(ITriggerServiceProviderAccessor triggerServiceProviderAccessor, ITriggerTypeRegistryService triggerTypeRegistryService, TriggerFactory triggerFactory)
2121
{
22-
_triggerServiceProviderAccessor = triggerServiceProviderAccessor ?? throw new ArgumentNullException(nameof(triggerServiceProviderAccessor));
22+
_triggerServiceProviderAccessor = triggerServiceProviderAccessor;
2323
_triggerTypeRegistryService = triggerTypeRegistryService ?? throw new ArgumentNullException(nameof(triggerTypeRegistryService));
24+
_triggerFactory = triggerFactory;
2425
}
2526

2627
public IEnumerable<TriggerDescriptor> DiscoverTriggers(Type openTriggerType, Type entityType, Func<Type, ITriggerTypeDescriptor> triggerTypeDescriptorFactory)
2728
{
28-
IServiceProvider serviceProvider = ServiceProvider;
29-
3029
var registry = _triggerTypeRegistryService.ResolveRegistry(openTriggerType, entityType, triggerTypeDescriptorFactory);
3130

3231
var triggerTypeDescriptors = registry.GetTriggerTypeDescriptors();
@@ -40,7 +39,7 @@ public IEnumerable<TriggerDescriptor> DiscoverTriggers(Type openTriggerType, Typ
4039

4140
foreach (var triggerTypeDescriptor in triggerTypeDescriptors)
4241
{
43-
var triggers = serviceProvider.GetServices(triggerTypeDescriptor.TriggerType);
42+
var triggers = _triggerFactory.Resolve(ServiceProvider, triggerTypeDescriptor.TriggerType);
4443
foreach (var trigger in triggers)
4544
{
4645
if (triggerDescriptors == null)
@@ -67,6 +66,23 @@ public IEnumerable<TriggerDescriptor> DiscoverTriggers(Type openTriggerType, Typ
6766
}
6867
}
6968

69+
public IEnumerable<TTrigger> DiscoverTriggers<TTrigger>()
70+
{
71+
// We can skip the registry as there is no generic argument
72+
var triggers = _triggerFactory.Resolve(ServiceProvider, typeof(TTrigger));
73+
74+
return triggers
75+
.Select((trigger, index) => (
76+
trigger,
77+
defaultPriority: index,
78+
customPriority: (trigger as ITriggerPriority)?.Priority ?? 0
79+
))
80+
.OrderBy(x => x.customPriority)
81+
.ThenBy(x => x.defaultPriority)
82+
.Select(x => x.trigger)
83+
.Cast<TTrigger>();
84+
}
85+
7086
public IServiceProvider ServiceProvider
7187
{
7288
get
@@ -86,7 +102,7 @@ public void ResetState()
86102
_serviceProvider = null;
87103
}
88104

89-
public Task ResetStateAsync(CancellationToken cancellationToken = default)
105+
public Task ResetStateAsync(CancellationToken cancellationToken = default)
90106
{
91107
ResetState();
92108
return Task.CompletedTask;
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Diagnostics;
4+
using System.Linq;
5+
using System.Text;
6+
using System.Threading.Tasks;
7+
using Microsoft.Extensions.DependencyInjection;
8+
9+
namespace EntityFrameworkCore.Triggered.Internal
10+
{
11+
public sealed class TriggerFactory
12+
{
13+
readonly IServiceProvider _internalServiceProvider;
14+
15+
public TriggerFactory(IServiceProvider internalServiceProvider)
16+
{
17+
_internalServiceProvider = internalServiceProvider;
18+
}
19+
20+
public IEnumerable<object> Resolve(IServiceProvider serviceProvider, Type triggerType)
21+
{
22+
// triggers may be directly registered with our DI container
23+
var triggers = serviceProvider.GetServices(triggerType);
24+
foreach (var trigger in triggers)
25+
{
26+
if (trigger is not null)
27+
{
28+
yield return trigger;
29+
}
30+
}
31+
32+
// Alternatively, triggers may be registered with the extension configuration
33+
var triggerServiceFactories = _internalServiceProvider.GetServices(typeof(ITriggerInstanceFactory<>).MakeGenericType(triggerType)).Cast<ITriggerInstanceFactory>();
34+
foreach (var triggerServiceFactory in triggerServiceFactories)
35+
{
36+
yield return triggerServiceFactory.Create(serviceProvider);
37+
}
38+
}
39+
}
40+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
using Microsoft.Extensions.DependencyInjection;
7+
8+
namespace EntityFrameworkCore.Triggered.Internal
9+
{
10+
11+
public interface ITriggerInstanceFactory
12+
{
13+
object Create(IServiceProvider serviceProvider);
14+
}
15+
16+
public interface ITriggerInstanceFactory<out TTriggerType> : ITriggerInstanceFactory
17+
{
18+
19+
}
20+
21+
public sealed class TriggerInstanceFactory<TTriggerType> : ITriggerInstanceFactory<TTriggerType>
22+
{
23+
readonly object? _serviceInstance;
24+
25+
public TriggerInstanceFactory(object? serviceInstance)
26+
{
27+
_serviceInstance = serviceInstance;
28+
}
29+
30+
public object Create(IServiceProvider serviceProvider)
31+
{
32+
if (_serviceInstance is not null)
33+
{
34+
return _serviceInstance;
35+
}
36+
37+
return ActivatorUtilities.GetServiceOrCreateInstance(serviceProvider, typeof(TTriggerType));
38+
}
39+
}
40+
}

0 commit comments

Comments
 (0)