6
6
7
7
using NHibernate . Action ;
8
8
using NHibernate . Cache ;
9
+ using NHibernate . Type ;
9
10
10
11
namespace NHibernate . Engine
11
12
{
@@ -505,13 +506,20 @@ private class InsertActionSorter
505
506
{
506
507
private readonly ActionQueue _actionQueue ;
507
508
508
- // the mapping of entity names to their latest batch numbers .
509
+ // The map of entity names to their latest batch.
509
510
private readonly Dictionary < string , int > _latestBatches = new Dictionary < string , int > ( ) ;
511
+ // The map of entities to their batch.
510
512
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 > ( ) ;
511
515
512
516
// the map of batch numbers to EntityInsertAction lists
513
517
private readonly Dictionary < int , List < EntityInsertAction > > _actionBatches = new Dictionary < int , List < EntityInsertAction > > ( ) ;
514
518
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>
515
523
public InsertActionSorter ( ActionQueue actionQueue )
516
524
{
517
525
_actionQueue = actionQueue ;
@@ -520,12 +528,16 @@ public InsertActionSorter(ActionQueue actionQueue)
520
528
_entityBatchNumber = new Dictionary < object , int > ( actionQueue . insertions . Count + 1 ) ;
521
529
}
522
530
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.
523
536
public void Sort ( )
524
537
{
525
- // the list of entity names that indicate the batch number
538
+ // build the map of entity names that indicate the batch number
526
539
foreach ( EntityInsertAction action in _actionQueue . insertions )
527
540
{
528
- // remove the current element from insertions. It will be added back later.
529
541
var entityName = action . EntityName ;
530
542
531
543
// the entity associated with the current action.
@@ -534,7 +546,9 @@ public void Sort()
534
546
var batchNumber = GetBatchNumber ( action , entityName ) ;
535
547
_entityBatchNumber [ currentEntity ] = batchNumber ;
536
548
AddToBatch ( batchNumber , action ) ;
549
+ UpdateChildrenDependencies ( batchNumber , action ) ;
537
550
}
551
+
538
552
_actionQueue . insertions . Clear ( ) ;
539
553
540
554
// 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)
555
569
{
556
570
// There is already an existing batch for this type of entity.
557
571
// Check to see if the latest batch is acceptable.
558
- if ( IsProcessedAfterAllAssociatedEntities ( action , batchNumber ) )
572
+ if ( ! RequireNewBatch ( action , batchNumber ) )
559
573
return batchNumber ;
560
574
}
561
575
@@ -565,36 +579,47 @@ private int GetBatchNumber(EntityInsertAction action, string entityName)
565
579
// so specify that this entity is with the latest batch.
566
580
// doing the batch number before adding the name to the list is
567
581
// a faster way to get an accurate number.
568
-
569
582
batchNumber = _actionBatches . Count ;
570
583
_latestBatches [ entityName ] = batchNumber ;
571
584
return batchNumber ;
572
585
}
573
586
574
- private bool IsProcessedAfterAllAssociatedEntities ( EntityInsertAction action , int latestBatchNumberForType )
587
+ private bool RequireNewBatch ( EntityInsertAction action , int latestBatchNumberForType )
575
588
{
589
+ // This method assumes the original action list is already sorted in order to respect dependencies.
576
590
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 ;
578
604
579
605
for ( var i = 0 ; i < propertyValues . Length ; i ++ )
580
606
{
581
607
var value = propertyValues [ i ] ;
582
608
var type = propertyTypes [ i ] ;
583
609
584
- if ( type . IsEntityType &&
585
- value != null )
610
+ if ( type . IsEntityType && value != null )
586
611
{
587
612
// find the batch number associated with the current association, if any.
588
613
int associationBatchNumber ;
589
614
if ( _entityBatchNumber . TryGetValue ( value , out associationBatchNumber ) &&
590
615
associationBatchNumber > latestBatchNumberForType )
591
616
{
592
- return false ;
617
+ return true ;
593
618
}
594
619
}
595
620
}
596
621
597
- return true ;
622
+ return false ;
598
623
}
599
624
600
625
private void AddToBatch ( int batchNumber , EntityInsertAction action )
@@ -609,6 +634,50 @@ private void AddToBatch(int batchNumber, EntityInsertAction action)
609
634
610
635
actions . Add ( action ) ;
611
636
}
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
+ }
612
681
}
613
682
}
614
683
}
0 commit comments