Skip to content

Commit c6b323e

Browse files
author
fabien.menager
committed
Improve tigger resolution
1 parent d777400 commit c6b323e

File tree

7 files changed

+244
-157
lines changed

7 files changed

+244
-157
lines changed

src/EntityFrameworkCore.Triggered.Extensions/ServiceCollectionExtensions.cs

Lines changed: 4 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,97 +1,16 @@
11
using System;
2-
using System.Collections.Concurrent;
3-
using System.Collections.Generic;
42
using System.Linq;
53
using System.Reflection;
6-
using EntityFrameworkCore.Triggered;
7-
using EntityFrameworkCore.Triggered.Lifecycles;
4+
using EntityFrameworkCore.Triggered.Extensions;
85
using Microsoft.Extensions.DependencyInjection.Extensions;
96

107
namespace Microsoft.Extensions.DependencyInjection
118
{
129
public static class ServiceCollectionExtensions
1310
{
14-
// Open generic trigger interfaces — add new generic lifecycle interfaces here
15-
private readonly static HashSet<Type> _genericTriggerTypes = new HashSet<Type>
16-
{
17-
typeof(IBeforeSaveTrigger<>),
18-
typeof(IBeforeSaveAsyncTrigger<>),
19-
typeof(IAfterSaveTrigger<>),
20-
typeof(IAfterSaveAsyncTrigger<>),
21-
typeof(IAfterSaveFailedTrigger<>),
22-
typeof(IAfterSaveFailedAsyncTrigger<>),
23-
};
24-
25-
// Non-generic lifecycle interfaces — add new non-generic lifecycle interfaces here
26-
private readonly static HashSet<Type> _nonGenericTriggerTypes = new HashSet<Type>
27-
{
28-
typeof(IBeforeSaveStartingTrigger),
29-
typeof(IBeforeSaveStartingAsyncTrigger),
30-
typeof(IBeforeSaveCompletedTrigger),
31-
typeof(IBeforeSaveCompletedAsyncTrigger),
32-
typeof(IAfterSaveFailedStartingTrigger),
33-
typeof(IAfterSaveFailedStartingAsyncTrigger),
34-
typeof(IAfterSaveFailedCompletedTrigger),
35-
typeof(IAfterSaveFailedCompletedAsyncTrigger),
36-
typeof(IAfterSaveStartingTrigger),
37-
typeof(IAfterSaveStartingAsyncTrigger),
38-
typeof(IAfterSaveCompletedTrigger),
39-
typeof(IAfterSaveCompletedAsyncTrigger),
40-
};
41-
42-
// Caches the resolved trigger interfaces per implementation type to avoid repeated reflection
43-
private readonly static ConcurrentDictionary<Type, Type[]> _triggerInterfaceCache =
44-
new ConcurrentDictionary<Type, Type[]>();
45-
46-
/// <summary>
47-
/// Returns the types defined in <paramref name="assembly"/>, gracefully handling
48-
/// <see cref="ReflectionTypeLoadException"/> that occurs when some types cannot be
49-
/// loaded due to missing dependencies (e.g. optional assemblies not present at runtime).
50-
/// </summary>
51-
private static IEnumerable<Type> GetAssemblyTypes(Assembly assembly)
52-
{
53-
try
54-
{
55-
return assembly.GetTypes();
56-
}
57-
catch (ReflectionTypeLoadException e)
58-
{
59-
// Return only the types that could be loaded; nulls represent types that failed
60-
return e.Types.OfType<Type>();
61-
}
62-
}
63-
64-
/// <summary>
65-
/// Returns the subset of <paramref name="triggerImplementationType"/>'s interfaces
66-
/// that match a known trigger interface, using a per-type cache.
67-
/// </summary>
68-
private static Type[] GetTriggerInterfaces(Type triggerImplementationType)
69-
=> _triggerInterfaceCache.GetOrAdd(triggerImplementationType, t =>
70-
{
71-
var interfaces = t.GetInterfaces();
72-
var result = new List<Type>(interfaces.Length);
73-
74-
foreach (var iface in interfaces)
75-
{
76-
if (iface.IsConstructedGenericType)
77-
{
78-
if (_genericTriggerTypes.Contains(iface.GetGenericTypeDefinition()))
79-
{
80-
result.Add(iface);
81-
}
82-
}
83-
else if (_nonGenericTriggerTypes.Contains(iface))
84-
{
85-
result.Add(iface);
86-
}
87-
}
88-
89-
return result.ToArray();
90-
});
91-
9211
private static void RegisterTriggerTypes(Type triggerImplementationType, IServiceCollection services)
9312
{
94-
var triggerInterfaces = GetTriggerInterfaces(triggerImplementationType);
13+
var triggerInterfaces = TriggerTypeHelper.GetTriggerInterfaces(triggerImplementationType);
9514

9615
foreach (var triggerInterface in triggerInterfaces)
9716
{
@@ -144,13 +63,11 @@ public static IServiceCollection AddAssemblyTriggers(this IServiceCollection ser
14463
return services;
14564
}
14665

147-
var assemblyTypes = assemblies
148-
.SelectMany(GetAssemblyTypes)
149-
.Where(x => x is { IsClass: true, IsAbstract: false });
66+
var assemblyTypes = assemblies.SelectMany(TriggerTypeHelper.GetAssemblyConcreteClasses);
15067

15168
foreach (var assemblyType in assemblyTypes)
15269
{
153-
var triggerInterfaces = GetTriggerInterfaces(assemblyType);
70+
var triggerInterfaces = TriggerTypeHelper.GetTriggerInterfaces(assemblyType);
15471

15572
if (triggerInterfaces.Length == 0)
15673
{
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
using System;
2+
using System.Collections.Concurrent;
3+
using System.Collections.Generic;
4+
using System.Linq;
5+
using System.Reflection;
6+
using EntityFrameworkCore.Triggered.Lifecycles;
7+
8+
namespace EntityFrameworkCore.Triggered.Extensions
9+
{
10+
/// <summary>
11+
/// Internal helpers shared between <c>ServiceCollectionExtensions</c> and
12+
/// <c>TriggersContextOptionsBuilderExtensions</c> for trigger-type discovery.
13+
/// </summary>
14+
static internal class TriggerTypeHelper
15+
{
16+
// Open generic trigger interfaces — add new generic lifecycle interfaces here
17+
private readonly static HashSet<Type> _genericTriggerTypes = new HashSet<Type>
18+
{
19+
typeof(IBeforeSaveTrigger<>),
20+
typeof(IBeforeSaveAsyncTrigger<>),
21+
typeof(IAfterSaveTrigger<>),
22+
typeof(IAfterSaveAsyncTrigger<>),
23+
typeof(IAfterSaveFailedTrigger<>),
24+
typeof(IAfterSaveFailedAsyncTrigger<>),
25+
};
26+
27+
// Non-generic lifecycle interfaces — add new non-generic lifecycle interfaces here
28+
private readonly static HashSet<Type> _nonGenericTriggerTypes = new HashSet<Type>
29+
{
30+
typeof(IBeforeSaveStartingTrigger),
31+
typeof(IBeforeSaveStartingAsyncTrigger),
32+
typeof(IBeforeSaveCompletedTrigger),
33+
typeof(IBeforeSaveCompletedAsyncTrigger),
34+
typeof(IAfterSaveFailedStartingTrigger),
35+
typeof(IAfterSaveFailedStartingAsyncTrigger),
36+
typeof(IAfterSaveFailedCompletedTrigger),
37+
typeof(IAfterSaveFailedCompletedAsyncTrigger),
38+
typeof(IAfterSaveStartingTrigger),
39+
typeof(IAfterSaveStartingAsyncTrigger),
40+
typeof(IAfterSaveCompletedTrigger),
41+
typeof(IAfterSaveCompletedAsyncTrigger),
42+
};
43+
44+
// Caches the resolved trigger interfaces per implementation type to avoid repeated reflection
45+
private readonly static ConcurrentDictionary<Type, Type[]> _triggerInterfaceCache =
46+
new ConcurrentDictionary<Type, Type[]>();
47+
48+
/// <summary>
49+
/// Returns the types defined in <paramref name="assembly"/>, gracefully handling
50+
/// <see cref="ReflectionTypeLoadException"/> that occurs when some types cannot be
51+
/// loaded due to missing dependencies (e.g. optional assemblies not present at runtime).
52+
/// </summary>
53+
private static IEnumerable<Type> GetAssemblyTypes(Assembly assembly)
54+
{
55+
try
56+
{
57+
return assembly.GetTypes();
58+
}
59+
catch (ReflectionTypeLoadException e)
60+
{
61+
// Return only the types that could be loaded; nulls represent types that failed
62+
return e.Types.OfType<Type>();
63+
}
64+
}
65+
66+
/// <summary>
67+
/// Returns the non-abstract classes defined in <paramref name="assembly"/>, gracefully handling
68+
/// <see cref="ReflectionTypeLoadException"/> that occurs when some types cannot be loaded due to missing
69+
/// dependencies (e.g. optional assemblies not present at runtime).
70+
/// </summary>
71+
static internal IEnumerable<Type> GetAssemblyConcreteClasses(Assembly assembly) =>
72+
GetAssemblyTypes(assembly).Where(t => t is { IsClass: true, IsAbstract: false });
73+
74+
/// <summary>
75+
/// Returns the subset of <paramref name="triggerImplementationType"/>'s interfaces that
76+
/// match a known trigger interface, using a per-type cache.
77+
/// </summary>
78+
static internal Type[] GetTriggerInterfaces(Type triggerImplementationType)
79+
=> _triggerInterfaceCache.GetOrAdd(triggerImplementationType, t =>
80+
{
81+
var interfaces = t.GetInterfaces();
82+
var result = new List<Type>(interfaces.Length);
83+
84+
foreach (var iface in interfaces)
85+
{
86+
if (iface.IsConstructedGenericType)
87+
{
88+
if (_genericTriggerTypes.Contains(iface.GetGenericTypeDefinition()))
89+
{
90+
result.Add(iface);
91+
}
92+
}
93+
else if (_nonGenericTriggerTypes.Contains(iface))
94+
{
95+
result.Add(iface);
96+
}
97+
}
98+
99+
return result.ToArray();
100+
});
101+
}
102+
}
103+
104+
105+

src/EntityFrameworkCore.Triggered.Extensions/TriggersContextOptionsBuilderExtensions.cs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Linq;
33
using System.Reflection;
4+
using EntityFrameworkCore.Triggered.Extensions;
45
using EntityFrameworkCore.Triggered.Infrastructure;
56
using Microsoft.Extensions.DependencyInjection;
67

@@ -24,14 +25,20 @@ public static TriggersContextOptionsBuilder AddAssemblyTriggers(this TriggersCon
2425
throw new ArgumentNullException(nameof(assemblies));
2526
}
2627

27-
var assemblyTypes = assemblies
28-
.SelectMany(x => x.GetTypes())
29-
.Where(x => x.IsClass)
30-
.Where(x => !x.IsAbstract);
28+
if (assemblies.Length == 0)
29+
{
30+
return builder;
31+
}
32+
33+
var assemblyTypes = assemblies.SelectMany(TriggerTypeHelper.GetAssemblyConcreteClasses);
3134

3235
foreach (var assemblyType in assemblyTypes)
3336
{
34-
builder.AddTrigger(assemblyType, lifetime);
37+
// Only register types that actually implement a known trigger interface
38+
if (TriggerTypeHelper.GetTriggerInterfaces(assemblyType).Length > 0)
39+
{
40+
builder.AddTrigger(assemblyType, lifetime);
41+
}
3542
}
3643

3744
return builder;

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -257,13 +257,13 @@ public void Validate(IDbContextOptions options) { }
257257

258258
private bool TypeIsValidTrigger(Type type)
259259
{
260-
if (TypeHelpers.FindGenericInterfaces(type, typeof(IBeforeSaveTrigger<>)) != null || TypeHelpers.FindGenericInterfaces(type, typeof(IAfterSaveTrigger<>)) != null)
260+
if (TypeHelpers.FindGenericInterfaces(type, typeof(IBeforeSaveTrigger<>)).Any() || TypeHelpers.FindGenericInterfaces(type, typeof(IAfterSaveTrigger<>)).Any())
261261
{
262262
return true;
263263
}
264264
else if (_triggerTypes != null)
265265
{
266-
return _triggerTypes.Any(triggerType => TypeHelpers.FindGenericInterfaces(type, triggerType) != null);
266+
return _triggerTypes.Any(triggerType => TypeHelpers.FindGenericInterfaces(type, triggerType).Any());
267267
}
268268
else
269269
{

0 commit comments

Comments
 (0)