Skip to content

Commit 3251440

Browse files
NH-3931 - fixing batch insert ordering issues.
1 parent 400f866 commit 3251440

File tree

1 file changed

+80
-11
lines changed

1 file changed

+80
-11
lines changed

src/NHibernate/Engine/ActionQueue.cs

Lines changed: 80 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,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

Comments
 (0)