@@ -70,7 +70,6 @@ public virtual IEnumerable<TEntity> BeforeCreate<TEntity>(IEnumerable<TEntity> e
70
70
node . UpdateUnique ( updated ) ;
71
71
node . Reassign ( entities ) ;
72
72
}
73
-
74
73
FireNestedBeforeUpdateHooks ( pipeline , _traversalHelper . CreateNextLayer ( node ) ) ;
75
74
return entities ;
76
75
}
@@ -234,59 +233,105 @@ void RecursiveBeforeRead(ContextEntity contextEntity, List<string> relationshipC
234
233
}
235
234
236
235
/// <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"/>
242
237
/// </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>
243
245
void FireNestedBeforeUpdateHooks ( ResourcePipeline pipeline , EntityChildLayer layer )
244
246
{
245
247
foreach ( IEntityNode node in layer )
246
248
{
247
249
var nestedHookcontainer = _executorHelper . GetResourceHookContainer ( node . EntityType , ResourceHook . BeforeUpdateRelationship ) ;
248
250
IEnumerable uniqueEntities = node . UniqueEntities ;
249
251
DependentType entityType = node . EntityType ;
252
+ Dictionary < RelationshipAttribute , IEnumerable > currenEntitiesGrouped ;
253
+ Dictionary < RelationshipAttribute , IEnumerable > currentEntitiesGroupedInverse ;
250
254
251
- // fire the BeforeUpdateRelationship hook for o2
255
+ // fire the BeforeUpdateRelationship hook for owner_new
252
256
if ( nestedHookcontainer != null )
253
257
{
254
258
if ( uniqueEntities . Cast < IIdentifiable > ( ) . Any ( ) )
255
259
{
256
260
var relationships = node . RelationshipsToNextLayer . Select ( p => p . Attribute ) . ToArray ( ) ;
257
261
var dbValues = LoadDbValues ( entityType , uniqueEntities , ResourceHook . BeforeUpdateRelationship , relationships ) ;
258
262
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 ) ;
263
274
var allowedIds = CallHook ( nestedHookcontainer , ResourceHook . BeforeUpdateRelationship , new object [ ] { GetIds ( uniqueEntities ) , resourcesByRelationship , pipeline } ) . Cast < string > ( ) ;
264
275
var updated = GetAllowedEntities ( uniqueEntities , allowedIds ) ;
265
276
node . UpdateUnique ( updated ) ;
266
277
node . Reassign ( ) ;
267
278
}
268
279
}
269
280
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 )
273
285
{
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
+ }
278
296
}
279
297
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 ( ) )
283
303
{
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 ) ;
286
316
}
287
317
}
288
318
}
289
319
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
+
290
335
/// <summary>
291
336
/// Given a source of entities, gets the implicitly affected entities
292
337
/// from the database and calls the BeforeImplicitUpdateRelationship hook.
@@ -317,16 +362,6 @@ void ValidateHookResponse<T>(IEnumerable<T> returnedList, ResourcePipeline pipel
317
362
}
318
363
}
319
364
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
- }
330
365
331
366
/// <summary>
332
367
/// A helper method to call a hook on <paramref name="container"/> reflectively.
@@ -389,26 +424,46 @@ HashSet<IIdentifiable> GetAllowedEntities(IEnumerable source, IEnumerable<string
389
424
return new HashSet < IIdentifiable > ( source . Cast < IIdentifiable > ( ) . Where ( ue => allowedIds . Contains ( ue . StringId ) ) ) ;
390
425
}
391
426
427
+
392
428
/// <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.
394
431
/// </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 )
396
439
{
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 ) ;
398
444
}
399
445
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
- }
405
446
447
+ /// <summary>
448
+ /// Fires the AfterUpdateRelationship hook
449
+ /// </summary>
406
450
void FireAfterUpdateRelationship ( IResourceHookContainer container , IEntityNode node , ResourcePipeline pipeline )
407
451
{
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 ) ) ;
409
459
CallHook ( container , ResourceHook . AfterUpdateRelationship , new object [ ] { resourcesByRelationship , pipeline } ) ;
410
460
}
411
461
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>
412
467
HashSet < string > GetIds ( IEnumerable entities )
413
468
{
414
469
return new HashSet < string > ( entities . Cast < IIdentifiable > ( ) . Select ( e => e . StringId ) ) ;
0 commit comments