@@ -70,7 +70,6 @@ public virtual IEnumerable<TEntity> BeforeCreate<TEntity>(IEnumerable<TEntity> e
7070 node . UpdateUnique ( updated ) ;
7171 node . Reassign ( entities ) ;
7272 }
73-
7473 FireNestedBeforeUpdateHooks ( pipeline , _traversalHelper . CreateNextLayer ( node ) ) ;
7574 return entities ;
7675 }
@@ -234,59 +233,105 @@ void RecursiveBeforeRead(ContextEntity contextEntity, List<string> relationshipC
234233 }
235234
236235 /// <summary>
237- /// Fires the nested before hooks. For example consider the case when
238- /// the owner of an article a1 (one-to-one) was updated from o1 to o2, where o2
239- /// was already related to a2. Then, the BeforeUpdateRelationship should be
240- /// fired for o2, and the BeforeImplicitUpdateRelationship hook should be fired for
241- /// o2 and then too for a2.
236+ /// Fires the nested before hooks for entities in the current <paramref name="layer"/>
242237 /// </summary>
238+ /// <remarks>
239+ /// For example: consider the case when the owner of article1 (one-to-one)
240+ /// is being updated from owner_old to owner_new, where owner_new is currently already
241+ /// related to article2. Then, the following nested hooks need to be fired in the following order.
242+ /// First the BeforeUpdateRelationship should be for owner1, then the
243+ /// BeforeImplicitUpdateRelationship hook should be fired for
244+ /// owner2, and lastely the BeforeImplicitUpdateRelationship for article2.</remarks>
243245 void FireNestedBeforeUpdateHooks ( ResourcePipeline pipeline , EntityChildLayer layer )
244246 {
245247 foreach ( IEntityNode node in layer )
246248 {
247249 var nestedHookcontainer = _executorHelper . GetResourceHookContainer ( node . EntityType , ResourceHook . BeforeUpdateRelationship ) ;
248250 IEnumerable uniqueEntities = node . UniqueEntities ;
249251 DependentType entityType = node . EntityType ;
252+ Dictionary < RelationshipAttribute , IEnumerable > currenEntitiesGrouped ;
253+ Dictionary < RelationshipAttribute , IEnumerable > currentEntitiesGroupedInverse ;
250254
251- // fire the BeforeUpdateRelationship hook for o2
255+ // fire the BeforeUpdateRelationship hook for owner_new
252256 if ( nestedHookcontainer != null )
253257 {
254258 if ( uniqueEntities . Cast < IIdentifiable > ( ) . Any ( ) )
255259 {
256260 var relationships = node . RelationshipsToNextLayer . Select ( p => p . Attribute ) . ToArray ( ) ;
257261 var dbValues = LoadDbValues ( entityType , uniqueEntities , ResourceHook . BeforeUpdateRelationship , relationships ) ;
258262
259- var dependentByPrevLayerRelationships = node . RelationshipsFromPreviousLayer . GetDependentEntities ( ) ;
260- var principalsByCurrentLayerRelationships = dependentByPrevLayerRelationships . ToDictionary ( kvp => _graph . GetInverseRelationship ( kvp . Key ) , kvp => kvp . Value ) ;
261-
262- var resourcesByRelationship = CreateRelationshipHelper ( entityType , principalsByCurrentLayerRelationships , dbValues ) ;
263+ /// these are the entities of the current node grouped by
264+ /// RelationshipAttributes that occured in the previous layer
265+ /// so it looks like { HasOneAttribute:owner => owner_new }.
266+ /// Note that in the BeforeUpdateRelationship hook of Person,
267+ /// we want want inverse relationship attribute:
268+ /// we now have the one pointing from article -> person, ]
269+ /// but we require the the one that points from person -> article
270+ currenEntitiesGrouped = node . RelationshipsFromPreviousLayer . GetDependentEntities ( ) ;
271+ currentEntitiesGroupedInverse = ReplaceKeysWithInverseRelationships ( currenEntitiesGrouped ) ;
272+
273+ var resourcesByRelationship = CreateRelationshipHelper ( entityType , currentEntitiesGroupedInverse , dbValues ) ;
263274 var allowedIds = CallHook ( nestedHookcontainer , ResourceHook . BeforeUpdateRelationship , new object [ ] { GetIds ( uniqueEntities ) , resourcesByRelationship , pipeline } ) . Cast < string > ( ) ;
264275 var updated = GetAllowedEntities ( uniqueEntities , allowedIds ) ;
265276 node . UpdateUnique ( updated ) ;
266277 node . Reassign ( ) ;
267278 }
268279 }
269280
270- // fire the BeforeImplicitUpdateRelationship hook for o1
271- var implicitPrincipalTargets = node . RelationshipsFromPreviousLayer . GetPrincipalEntities ( ) ;
272- if ( pipeline != ResourcePipeline . Post && implicitPrincipalTargets . Any ( ) )
281+ /// Fire the BeforeImplicitUpdateRelationship hook for owner_old.
282+ /// Note: if the pipeline is Post it means we just created article1,
283+ /// which means we are sure that it isn't related to any other entities yet.
284+ if ( pipeline != ResourcePipeline . Post )
273285 {
274- // value in implicitPrincipalTargets is a1 here.
275- // we need to load the current value in db, which is o1.
276- // then we need to inverse the relationship attribute
277- FireForAffectedImplicits ( entityType , implicitPrincipalTargets , pipeline , uniqueEntities ) ;
286+ /// To fire a hook for owner_old, we need to first get a reference to it.
287+ /// For this, we need to query the database for the HasOneAttribute:owner
288+ /// relationship of article1, which is referred to as the
289+ /// principal side of the HasOneAttribute:owner relationship.
290+ var principalEntities = node . RelationshipsFromPreviousLayer . GetPrincipalEntities ( ) ;
291+ if ( principalEntities . Any ( ) )
292+ {
293+ /// owner_old is loaded, which is an "implicitly affected entity"
294+ FireForAffectedImplicits ( entityType , principalEntities , pipeline , uniqueEntities ) ;
295+ }
278296 }
279297
280- // fire the BeforeImplicitUpdateRelationship hook for a2
281- var dependentEntities = node . RelationshipsFromPreviousLayer . GetDependentEntities ( ) ;
282- if ( dependentEntities . Any ( ) )
298+ /// Fire the BeforeImplicitUpdateRelationship hook for article2
299+ /// For this, we need to query the database for the current owner
300+ /// relationship value of owner_new.
301+ currenEntitiesGrouped = node . RelationshipsFromPreviousLayer . GetDependentEntities ( ) ;
302+ if ( currenEntitiesGrouped . Any ( ) )
283303 {
284- ( var implicitDependentTargets , var principalEntityType ) = GetDependentImplicitsTargets ( dependentEntities ) ;
285- FireForAffectedImplicits ( principalEntityType , implicitDependentTargets , pipeline ) ;
304+ /// dependentEntities is grouped by relationships from previous
305+ /// layer, ie { HasOneAttribute:owner => owner_new }. But
306+ /// to load article2 onto owner_new, we need to have the
307+ /// RelationshipAttribute from owner to article, which is the
308+ /// inverse of HasOneAttribute:owner
309+ currentEntitiesGroupedInverse = ReplaceKeysWithInverseRelationships ( currenEntitiesGrouped ) ;
310+ /// Note that currently in the JADNC implementation of hooks,
311+ /// the root layer is ALWAYS homogenous, so we safely assume
312+ /// that for every relationship to the previous layer, the
313+ /// principal type is the same.
314+ PrincipalType principalEntityType = currenEntitiesGrouped . First ( ) . Key . PrincipalType ;
315+ FireForAffectedImplicits ( principalEntityType , currentEntitiesGroupedInverse , pipeline ) ;
286316 }
287317 }
288318 }
289319
320+ /// <summary>
321+ /// replaces the keys of the <paramref name="entitiesByRelationship"/> dictionary
322+ /// with its inverse relationship attribute.
323+ /// </summary>
324+ /// <param name="entitiesByRelationship">Entities grouped by relationship attribute</param>
325+ Dictionary < RelationshipAttribute , IEnumerable > ReplaceKeysWithInverseRelationships ( Dictionary < RelationshipAttribute , IEnumerable > entitiesByRelationship )
326+ {
327+ /// when Article has one Owner (HasOneAttribute:owner) is set, there is no guarantee
328+ /// that the inverse attribute was also set (Owner has one Article: HasOneAttr:article).
329+ /// If it isn't, JADNC currently knows nothing about this relationship pointing back, and it
330+ /// currently cannot fire hooks for entities resolved through inverse relationships.
331+ var inversableRelationshipAttributes = entitiesByRelationship . Where ( kvp => kvp . Key . InverseNavigation != null ) ;
332+ return inversableRelationshipAttributes . ToDictionary ( kvp => _graph . GetInverseRelationship ( kvp . Key ) , kvp => kvp . Value ) ;
333+ }
334+
290335 /// <summary>
291336 /// Given a source of entities, gets the implicitly affected entities
292337 /// from the database and calls the BeforeImplicitUpdateRelationship hook.
@@ -317,16 +362,6 @@ void ValidateHookResponse<T>(IEnumerable<T> returnedList, ResourcePipeline pipel
317362 }
318363 }
319364
320- /// <summary>
321- /// NOTE: in JADNC usage, the root layer is ALWAYS homogenous, so we can be sure that for every
322- /// relationship to the previous layer, the principal type is the same.
323- /// </summary>
324- ( Dictionary < RelationshipAttribute , IEnumerable > , PrincipalType ) GetDependentImplicitsTargets ( Dictionary < RelationshipAttribute , IEnumerable > dependentEntities )
325- {
326- PrincipalType principalType = dependentEntities . First ( ) . Key . PrincipalType ;
327- var byInverseRelationship = dependentEntities . Where ( kvp => kvp . Key . InverseNavigation != null ) . ToDictionary ( kvp => GetInverseRelationship ( kvp . Key ) , kvp => kvp . Value ) ;
328- return ( byInverseRelationship , principalType ) ;
329- }
330365
331366 /// <summary>
332367 /// A helper method to call a hook on <paramref name="container"/> reflectively.
@@ -389,26 +424,46 @@ HashSet<IIdentifiable> GetAllowedEntities(IEnumerable source, IEnumerable<string
389424 return new HashSet < IIdentifiable > ( source . Cast < IIdentifiable > ( ) . Where ( ue => allowedIds . Contains ( ue . StringId ) ) ) ;
390425 }
391426
427+
392428 /// <summary>
393- /// Gets the inverse <see cref="RelationshipAttribute"/> for <paramref name="attribute"/>
429+ /// given the set of <paramref name="uniqueEntities"/>, it will load all the
430+ /// values from the database of these entites.
394431 /// </summary>
395- RelationshipAttribute GetInverseRelationship ( RelationshipAttribute attribute )
432+ /// <returns>The db values.</returns>
433+ /// <param name="entityType">type of the entities to be loaded</param>
434+ /// <param name="uniqueEntities">The set of entities to load the db values for</param>
435+ /// <param name="targetHook">The hook in which the db values will be displayed.</param>
436+ /// <param name="relationshipsToNextLayer">Relationships from <paramref name="entityType"/> to the next layer:
437+ /// this indicates which relationships will be included on <paramref name="uniqueEntities"/>.</param>
438+ IEnumerable LoadDbValues ( Type entityType , IEnumerable uniqueEntities , ResourceHook targetHook , RelationshipAttribute [ ] relationshipsToNextLayer )
396439 {
397- return _graph . GetInverseRelationship ( attribute ) ;
440+ /// We only need to load database values if the target hook of this hook execution
441+ /// cycle is compatible with displaying database values and has this option enabled.
442+ if ( ! _executorHelper . ShouldLoadDbValues ( entityType , targetHook ) ) return null ;
443+ return _executorHelper . LoadDbValues ( entityType , uniqueEntities , targetHook , relationshipsToNextLayer ) ;
398444 }
399445
400- IEnumerable LoadDbValues ( Type containerEntityType , IEnumerable uniqueEntities , ResourceHook targetHook , RelationshipAttribute [ ] relationshipsToNextLayer )
401- {
402- if ( ! _executorHelper . ShouldLoadDbValues ( containerEntityType , targetHook ) ) return null ;
403- return _executorHelper . LoadDbValues ( containerEntityType , uniqueEntities , targetHook , relationshipsToNextLayer ) ;
404- }
405446
447+ /// <summary>
448+ /// Fires the AfterUpdateRelationship hook
449+ /// </summary>
406450 void FireAfterUpdateRelationship ( IResourceHookContainer container , IEntityNode node , ResourcePipeline pipeline )
407451 {
408- var resourcesByRelationship = CreateRelationshipHelper ( node . EntityType , node . RelationshipsFromPreviousLayer . GetDependentEntities ( ) ) ;
452+
453+ Dictionary < RelationshipAttribute , IEnumerable > currenEntitiesGrouped = node . RelationshipsFromPreviousLayer . GetDependentEntities ( ) ;
454+ /// the relationships attributes in currenEntitiesGrouped will be pointing from a
455+ /// resource in the previouslayer to a resource in the current (nested) layer.
456+ /// For the nested hook we need to replace these attributes with their inverse.
457+ /// See the FireNestedBeforeUpdateHooks method for a more detailed example.
458+ var resourcesByRelationship = CreateRelationshipHelper ( node . EntityType , ReplaceKeysWithInverseRelationships ( currenEntitiesGrouped ) ) ;
409459 CallHook ( container , ResourceHook . AfterUpdateRelationship , new object [ ] { resourcesByRelationship , pipeline } ) ;
410460 }
411461
462+ /// <summary>
463+ /// Returns a list of StringIds from a list of IIdentifiables (<paramref name="entities"/>).
464+ /// </summary>
465+ /// <returns>The ids.</returns>
466+ /// <param name="entities">iidentifiable entities.</param>
412467 HashSet < string > GetIds ( IEnumerable entities )
413468 {
414469 return new HashSet < string > ( entities . Cast < IIdentifiable > ( ) . Select ( e => e . StringId ) ) ;
0 commit comments