66
77using NHibernate . Action ;
88using NHibernate . Cache ;
9+ using NHibernate . Type ;
910
1011namespace NHibernate . Engine
1112{
@@ -505,13 +506,20 @@ private class InsertActionSorter
505506 {
506507 private readonly ActionQueue _actionQueue ;
507508
508- // the mapping of entity names to their latest batch numbers .
509+ // The map of entity names to their latest batch.
509510 private readonly Dictionary < string , int > _latestBatches = new Dictionary < string , int > ( ) ;
511+ // The map of entities to their batch.
510512 private readonly Dictionary < object , int > _entityBatchNumber ;
513+ // The map of entities to the latest batch (of another entities) they depend on.
514+ private readonly Dictionary < object , int > _entityBatchDependency = new Dictionary < object , int > ( ) ;
511515
512516 // the map of batch numbers to EntityInsertAction lists
513517 private readonly Dictionary < int , List < EntityInsertAction > > _actionBatches = new Dictionary < int , List < EntityInsertAction > > ( ) ;
514518
519+ /// <summary>
520+ /// A sorter aiming to group inserts as much as possible for optimizing batching.
521+ /// </summary>
522+ /// <param name="actionQueue">The list of inserts to optimize, already sorted in order to avoid constraint violations.</param>
515523 public InsertActionSorter ( ActionQueue actionQueue )
516524 {
517525 _actionQueue = actionQueue ;
@@ -520,12 +528,16 @@ public InsertActionSorter(ActionQueue actionQueue)
520528 _entityBatchNumber = new Dictionary < object , int > ( actionQueue . insertions . Count + 1 ) ;
521529 }
522530
531+ // This sorting does not actually optimize some features like mapped inheritance or joined-table,
532+ // which causes additional inserts per action, causing the batcher to flush on each. Moreover,
533+ // inheritance may causes children entities batches to get split per concrete parent classes.
534+ // (See InsertOrderingFixture.WithJoinedTableInheritance by example.)
535+ // Trying to merge those children batches cases would probably require to much computing.
523536 public void Sort ( )
524537 {
525- // the list of entity names that indicate the batch number
538+ // build the map of entity names that indicate the batch number
526539 foreach ( EntityInsertAction action in _actionQueue . insertions )
527540 {
528- // remove the current element from insertions. It will be added back later.
529541 var entityName = action . EntityName ;
530542
531543 // the entity associated with the current action.
@@ -534,7 +546,9 @@ public void Sort()
534546 var batchNumber = GetBatchNumber ( action , entityName ) ;
535547 _entityBatchNumber [ currentEntity ] = batchNumber ;
536548 AddToBatch ( batchNumber , action ) ;
549+ UpdateChildrenDependencies ( batchNumber , action ) ;
537550 }
551+
538552 _actionQueue . insertions . Clear ( ) ;
539553
540554 // now rebuild the insertions list. There is a batch for each entry in the name list.
@@ -555,7 +569,7 @@ private int GetBatchNumber(EntityInsertAction action, string entityName)
555569 {
556570 // There is already an existing batch for this type of entity.
557571 // Check to see if the latest batch is acceptable.
558- if ( IsProcessedAfterAllAssociatedEntities ( action , batchNumber ) )
572+ if ( ! RequireNewBatch ( action , batchNumber ) )
559573 return batchNumber ;
560574 }
561575
@@ -565,36 +579,47 @@ private int GetBatchNumber(EntityInsertAction action, string entityName)
565579 // so specify that this entity is with the latest batch.
566580 // doing the batch number before adding the name to the list is
567581 // a faster way to get an accurate number.
568-
569582 batchNumber = _actionBatches . Count ;
570583 _latestBatches [ entityName ] = batchNumber ;
571584 return batchNumber ;
572585 }
573586
574- private bool IsProcessedAfterAllAssociatedEntities ( EntityInsertAction action , int latestBatchNumberForType )
587+ private bool RequireNewBatch ( EntityInsertAction action , int latestBatchNumberForType )
575588 {
589+ // This method assumes the original action list is already sorted in order to respect dependencies.
576590 var propertyValues = action . State ;
577- var propertyTypes = action . Persister . ClassMetadata . PropertyTypes ;
591+ var propertyTypes = action . Persister . EntityMetamodel ? . PropertyTypes ;
592+ if ( propertyTypes == null )
593+ {
594+ log . InfoFormat (
595+ "Entity {0} persister does not provide meta-data, giving up batching grouping optimization for this entity." ,
596+ action . EntityName ) ;
597+ // Cancel grouping optimization for this entity.
598+ return true ;
599+ }
600+
601+ int latestDependency ;
602+ if ( _entityBatchDependency . TryGetValue ( action . Instance , out latestDependency ) && latestDependency > latestBatchNumberForType )
603+ return true ;
578604
579605 for ( var i = 0 ; i < propertyValues . Length ; i ++ )
580606 {
581607 var value = propertyValues [ i ] ;
582608 var type = propertyTypes [ i ] ;
583609
584- if ( type . IsEntityType &&
585- value != null )
610+ if ( type . IsEntityType && value != null )
586611 {
587612 // find the batch number associated with the current association, if any.
588613 int associationBatchNumber ;
589614 if ( _entityBatchNumber . TryGetValue ( value , out associationBatchNumber ) &&
590615 associationBatchNumber > latestBatchNumberForType )
591616 {
592- return false ;
617+ return true ;
593618 }
594619 }
595620 }
596621
597- return true ;
622+ return false ;
598623 }
599624
600625 private void AddToBatch ( int batchNumber , EntityInsertAction action )
@@ -609,6 +634,50 @@ private void AddToBatch(int batchNumber, EntityInsertAction action)
609634
610635 actions . Add ( action ) ;
611636 }
637+
638+ private void UpdateChildrenDependencies ( int batchNumber , EntityInsertAction action )
639+ {
640+ var propertyValues = action . State ;
641+ var propertyTypes = action . Persister . EntityMetamodel ? . PropertyTypes ;
642+ if ( propertyTypes == null )
643+ {
644+ log . WarnFormat (
645+ "Entity {0} persister does not provide meta-data: if there is dependent entities providing " +
646+ "meta-data, they may get batched before this one and cause a failure." ,
647+ action . EntityName ) ;
648+ return ;
649+ }
650+
651+ var sessionFactory = action . Session . Factory ;
652+ for ( var i = 0 ; i < propertyValues . Length ; i ++ )
653+ {
654+ var type = propertyTypes [ i ] ;
655+
656+ if ( ! type . IsCollectionType )
657+ continue ;
658+
659+ var collectionType = ( CollectionType ) type ;
660+ var collectionPersister = sessionFactory . GetCollectionPersister ( collectionType . Role ) ;
661+ if ( collectionPersister . IsManyToMany || ! collectionPersister . ElementType . IsEntityType )
662+ continue ;
663+
664+ var children = propertyValues [ i ] as IEnumerable ;
665+ if ( children == null )
666+ continue ;
667+
668+ foreach ( var child in children )
669+ {
670+ if ( child == null )
671+ continue ;
672+
673+ int latestDependency ;
674+ if ( _entityBatchDependency . TryGetValue ( child , out latestDependency ) && latestDependency > batchNumber )
675+ continue ;
676+
677+ _entityBatchDependency [ child ] = batchNumber ;
678+ }
679+ }
680+ }
612681 }
613682 }
614683}
0 commit comments