11using System ;
2+ using System . Collections . Concurrent ;
23using System . Collections . Generic ;
34using System . Diagnostics . CodeAnalysis ;
45using System . Linq ;
@@ -18,6 +19,7 @@ public class DefaultHookHandler : IHookHandler
1819 // Prevents repetitive hooking of the same entity/state/[pre|post] combination within a single request
1920 private readonly HashSet < HookedEntityKey > _hookedEntities = new HashSet < HookedEntityKey > ( ) ;
2021
22+ private readonly static ConcurrentDictionary < Type , bool > _hookableEntities = new ConcurrentDictionary < Type , bool > ( ) ;
2123 private static HashSet < Type > _importantPreHookTypes ;
2224 private static HashSet < Type > _importantPostHookTypes ;
2325 private readonly static object _lock = new object ( ) ;
@@ -74,7 +76,13 @@ public bool TriggerPreActionHooks(IEnumerable<HookedEntityEntry> entries, bool r
7476 {
7577 bool anyStateChanged = false ;
7678
77- if ( entries == null || ! _preHooks . Any ( ) || ( importantHooksOnly && ! this . HasImportantPreHooks ( ) ) )
79+ if ( entries != null )
80+ {
81+ // Skip entities explicitly marked as unhookable
82+ entries = entries . Where ( IsHookableEntry ) ;
83+ }
84+
85+ if ( entries == null || ! entries . Any ( ) || ! _preHooks . Any ( ) || ( importantHooksOnly && ! this . HasImportantPreHooks ( ) ) )
7886 return false ;
7987
8088 var processedHooks = new HashSet < IPreActionHook > ( ) ;
@@ -83,7 +91,7 @@ public bool TriggerPreActionHooks(IEnumerable<HookedEntityEntry> entries, bool r
8391 {
8492 var e = entry ; // Prevents access to modified closure
8593 var entity = e . Entry . Entity as BaseEntity ;
86- if ( HookedAlready ( entity , e . PreSaveState , false ) )
94+ if ( HandledAlready ( entity , e . PreSaveState , false ) )
8795 {
8896 // Prevent repetitive hooking of the same entity/state/pre combination within a single request
8997 continue ;
@@ -154,7 +162,13 @@ private IEnumerable<IPreActionHook> GetPreHookInstancesFor(BaseEntity entity, bo
154162
155163 public void TriggerPostActionHooks ( IEnumerable < HookedEntityEntry > entries , bool importantHooksOnly )
156164 {
157- if ( entries == null || ! _postHooks . Any ( ) || ( importantHooksOnly && ! this . HasImportantPostHooks ( ) ) )
165+ if ( entries != null )
166+ {
167+ // Skip entities explicitly marked as unhookable
168+ entries = entries . Where ( IsHookableEntry ) ;
169+ }
170+
171+ if ( entries == null || ! entries . Any ( ) || ! _postHooks . Any ( ) || ( importantHooksOnly && ! this . HasImportantPostHooks ( ) ) )
158172 return ;
159173
160174 var processedHooks = new HashSet < IPostActionHook > ( ) ;
@@ -163,7 +177,7 @@ public void TriggerPostActionHooks(IEnumerable<HookedEntityEntry> entries, bool
163177 {
164178 var e = entry ; // Prevents access to modified closure
165179 var entity = e . Entry . Entity as BaseEntity ;
166- if ( HookedAlready ( entity , e . PreSaveState , true ) )
180+ if ( HandledAlready ( entity , e . PreSaveState , true ) )
167181 {
168182 // Prevent repetitive hooking of the same entity/state/post combination within a single request
169183 continue ;
@@ -221,7 +235,30 @@ private IEnumerable<IPostActionHook> GetPostHookInstancesFor(BaseEntity entity,
221235 return hooks ;
222236 }
223237
224- private bool HookedAlready ( BaseEntity entity , EntityState preSaveState , bool isPostActionHook )
238+ private bool IsHookableEntry ( HookedEntityEntry entry )
239+ {
240+ var entity = entry . Entry . Entity as BaseEntity ;
241+ if ( entity == null )
242+ {
243+ return false ;
244+ }
245+
246+ var isHookable = _hookableEntities . GetOrAdd ( entity . GetUnproxiedType ( ) , t =>
247+ {
248+ var attr = t . GetAttribute < HookableAttribute > ( true ) ;
249+ if ( attr != null )
250+ {
251+ return attr . IsHookable ;
252+ }
253+
254+ // Entities are hookable by default
255+ return true ;
256+ } ) ;
257+
258+ return isHookable ;
259+ }
260+
261+ private bool HandledAlready ( BaseEntity entity , EntityState preSaveState , bool isPostActionHook )
225262 {
226263 if ( entity . IsTransientRecord ( ) )
227264 return false ;
0 commit comments