@@ -15,6 +15,9 @@ public class DefaultHookHandler : IHookHandler
1515 private readonly Multimap < Type , IPreActionHook > _preHooksRequestCache = new Multimap < Type , IPreActionHook > ( ) ;
1616 private readonly Multimap < Type , IPostActionHook > _postHooksRequestCache = new Multimap < Type , IPostActionHook > ( ) ;
1717
18+ // Prevents repetitive hooking of the same entity/state/[pre|post] combination within a single request
19+ private readonly HashSet < HookedEntityKey > _hookedEntities = new HashSet < HookedEntityKey > ( ) ;
20+
1821 private static HashSet < Type > _importantPreHookTypes ;
1922 private static HashSet < Type > _importantPostHookTypes ;
2023 private readonly static object _lock = new object ( ) ;
@@ -79,7 +82,13 @@ public bool TriggerPreActionHooks(IEnumerable<HookedEntityEntry> entries, bool r
7982 foreach ( var entry in entries )
8083 {
8184 var e = entry ; // Prevents access to modified closure
82- var preHooks = GetPreHookInstancesFor ( e . Entry . Entity as BaseEntity , importantHooksOnly ) ;
85+ var entity = e . Entry . Entity as BaseEntity ;
86+ if ( HookedAlready ( entity , e . PreSaveState , false ) )
87+ {
88+ // Prevent repetitive hooking of the same entity/state/pre combination within a single request
89+ continue ;
90+ }
91+ var preHooks = GetPreHookInstancesFor ( entity , importantHooksOnly ) ;
8392 foreach ( var hook in preHooks )
8493 {
8594 if ( hook . CanProcess ( e . PreSaveState ) && hook . RequiresValidation == requiresValidation )
@@ -89,7 +98,7 @@ public bool TriggerPreActionHooks(IEnumerable<HookedEntityEntry> entries, bool r
8998 // call hook
9099 try
91100 {
92- hook . HookObject ( e . Entry . Entity , metadata ) ;
101+ hook . HookObject ( entity , metadata ) ;
93102 }
94103 catch ( Exception ex )
95104 {
@@ -153,7 +162,13 @@ public void TriggerPostActionHooks(IEnumerable<HookedEntityEntry> entries, bool
153162 foreach ( var entry in entries )
154163 {
155164 var e = entry ; // Prevents access to modified closure
156- var postHooks = GetPostHookInstancesFor ( e . Entry . Entity as BaseEntity , importantHooksOnly ) ;
165+ var entity = e . Entry . Entity as BaseEntity ;
166+ if ( HookedAlready ( entity , e . PreSaveState , true ) )
167+ {
168+ // Prevent repetitive hooking of the same entity/state/post combination within a single request
169+ continue ;
170+ }
171+ var postHooks = GetPostHookInstancesFor ( entity , importantHooksOnly ) ;
157172 foreach ( var hook in postHooks )
158173 {
159174 if ( hook . CanProcess ( e . PreSaveState ) )
@@ -163,7 +178,7 @@ public void TriggerPostActionHooks(IEnumerable<HookedEntityEntry> entries, bool
163178 // call hook
164179 try
165180 {
166- hook . HookObject ( e . Entry . Entity , metadata ) ;
181+ hook . HookObject ( entity , metadata ) ;
167182 }
168183 catch ( Exception ex )
169184 {
@@ -205,5 +220,28 @@ private IEnumerable<IPostActionHook> GetPostHookInstancesFor(BaseEntity entity,
205220
206221 return hooks ;
207222 }
223+
224+ private bool HookedAlready ( BaseEntity entity , EntityState preSaveState , bool isPostActionHook )
225+ {
226+ if ( entity . IsTransientRecord ( ) )
227+ return false ;
228+
229+ var key = new HookedEntityKey ( entity . GetUnproxiedType ( ) , entity . Id , preSaveState , isPostActionHook ) ;
230+ if ( _hookedEntities . Contains ( key ) )
231+ {
232+ return true ;
233+ }
234+
235+ _hookedEntities . Add ( key ) ;
236+ return false ;
237+ }
238+
239+ class HookedEntityKey : Tuple < Type , int , EntityState , bool >
240+ {
241+ public HookedEntityKey ( Type entityType , int entityId , EntityState preSaveState , bool isPostActionHook )
242+ : base ( entityType , entityId , preSaveState , isPostActionHook )
243+ {
244+ }
245+ }
208246 }
209247}
0 commit comments