Skip to content

Commit 9937f7b

Browse files
NH-3951 - fixing batch insert ordering issues.
1 parent 50eaf91 commit 9937f7b

File tree

1 file changed

+82
-11
lines changed

1 file changed

+82
-11
lines changed

src/NHibernate/Engine/ActionQueue.cs

Lines changed: 82 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
using NHibernate.Action;
88
using NHibernate.Cache;
9+
using NHibernate.Type;
910

1011
namespace 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,49 @@ 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];
608+
if (value == null)
609+
continue;
582610
var type = propertyTypes[i];
583611

584-
if (type.IsEntityType &&
585-
value != null)
612+
if (type.IsEntityType)
586613
{
587614
// find the batch number associated with the current association, if any.
588615
int associationBatchNumber;
589616
if (_entityBatchNumber.TryGetValue(value, out associationBatchNumber) &&
590617
associationBatchNumber > latestBatchNumberForType)
591618
{
592-
return false;
619+
return true;
593620
}
594621
}
595622
}
596623

597-
return true;
624+
return false;
598625
}
599626

600627
private void AddToBatch(int batchNumber, EntityInsertAction action)
@@ -609,6 +636,50 @@ private void AddToBatch(int batchNumber, EntityInsertAction action)
609636

610637
actions.Add(action);
611638
}
639+
640+
private void UpdateChildrenDependencies(int batchNumber, EntityInsertAction action)
641+
{
642+
var propertyValues = action.State;
643+
var propertyTypes = action.Persister.EntityMetamodel?.PropertyTypes;
644+
if (propertyTypes == null)
645+
{
646+
log.WarnFormat(
647+
"Entity {0} persister does not provide meta-data: if there is dependent entities providing " +
648+
"meta-data, they may get batched before this one and cause a failure.",
649+
action.EntityName);
650+
return;
651+
}
652+
653+
var sessionFactory = action.Session.Factory;
654+
for (var i = 0; i < propertyValues.Length; i++)
655+
{
656+
var type = propertyTypes[i];
657+
658+
if (!type.IsCollectionType)
659+
continue;
660+
661+
var collectionType = (CollectionType)type;
662+
var collectionPersister = sessionFactory.GetCollectionPersister(collectionType.Role);
663+
if (collectionPersister.IsManyToMany || !collectionPersister.ElementType.IsEntityType)
664+
continue;
665+
666+
var children = propertyValues[i] as IEnumerable;
667+
if (children == null)
668+
continue;
669+
670+
foreach(var child in children)
671+
{
672+
if (child == null)
673+
continue;
674+
675+
int latestDependency;
676+
if (_entityBatchDependency.TryGetValue(child, out latestDependency) && latestDependency > batchNumber)
677+
continue;
678+
679+
_entityBatchDependency[child] = batchNumber;
680+
}
681+
}
682+
}
612683
}
613684
}
614685
}

0 commit comments

Comments
 (0)