diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH4077/PostInsertFixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH4077/PostInsertFixture.cs
new file mode 100644
index 00000000000..50bdc4f532a
--- /dev/null
+++ b/src/NHibernate.Test/Async/NHSpecificTest/NH4077/PostInsertFixture.cs
@@ -0,0 +1,185 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by AsyncGenerator.
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+
+using System;
+using System.Linq;
+using NHibernate.Cfg;
+using NHibernate.Cfg.MappingSchema;
+using NHibernate.Event;
+using NHibernate.Mapping.ByCode;
+using NUnit.Framework;
+
+namespace NHibernate.Test.NHSpecificTest.NH4077
+{
+ using System.Threading.Tasks;
+ using System.Threading;
+ [TestFixture]
+ public partial class PostInsertFixtureAsync : TestCaseMappingByCode
+ {
+ [Test]
+ public async Task AutoflushInPostInsertListener_CausesDuplicateInserts_WithPrimaryKeyViolationsAsync()
+ {
+ using (var session = OpenSession())
+ using (var transaction = session.BeginTransaction())
+ {
+ // using FlushMode.Commit prevents the issue; using the default FlushMode.Auto breaks.
+ //session.FlushMode = FlushMode.Commit;
+ await (session.SaveAsync(new Entity { Code = "one" }));
+ await (session.SaveAsync(new Entity { Code = "two" }));
+
+ // committing the transaction causes a primary key violation by saving the entities multiple times
+ await (transaction.CommitAsync());
+ await (session.FlushAsync());
+ }
+ }
+
+ [Test]
+ public async Task Autoflush_MayTriggerAdditionalAutoFlushFromEventsAsync()
+ {
+ using (var session = OpenSession())
+ using (var transaction = session.BeginTransaction())
+ {
+ // using FlushMode.Commit prevents the issue; using the default FlushMode.Auto breaks.
+ //session.FlushMode = FlushMode.Commit;
+ await (session.SaveAsync(new Entity { Code = "one" }));
+ await (session.SaveAsync(new Entity { Code = "two" }));
+
+ // Querying the entity triggers an auto-flush
+ var count = await (session.CreateQuery("select count(o) from Entity o").UniqueResultAsync());
+ Assert.That(count, Is.GreaterThan(0));
+ await (transaction.CommitAsync());
+ await (session.FlushAsync());
+ }
+ }
+
+ protected override HbmMapping GetMappings()
+ {
+ var mapper = new ModelMapper();
+ mapper.Class(rc =>
+ {
+ rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb));
+ rc.Property(x => x.Code);
+ });
+
+ return mapper.CompileMappingForAllExplicitlyAddedEntities();
+ }
+
+ protected override void Configure(Configuration configuration)
+ {
+ base.Configure(configuration);
+ var existingListeners = (configuration.EventListeners.PostInsertEventListeners ?? new IPostInsertEventListener[0]).ToList();
+ // this evil listener uses the session to perform a few queries and causes an auto-flush to happen
+ existingListeners.Add(new CausesAutoflushListener());
+ configuration.EventListeners.PostInsertEventListeners = existingListeners.ToArray();
+ }
+
+ protected override void OnTearDown()
+ {
+ using (var session = OpenSession())
+ using (var transaction = session.BeginTransaction())
+ {
+ session.Delete("from Entity");
+
+ session.Flush();
+ transaction.Commit();
+ }
+ }
+
+ private sealed partial class CausesAutoflushListener : IPostInsertEventListener
+ {
+ private bool _currentlyLogging;
+
+ public async Task OnPostInsertAsync(PostInsertEvent @event, CancellationToken cancellationToken)
+ {
+ if (!(@event.Entity is Entity))
+ return;
+ // This guard is necessary to avoid multiple inserts of the original objects.
+ // Commenting this out is likely to cause one PK violation per run, which seems to be capped to at most 10 attempts.
+ // With the guard, only one PK violation is reported.
+ if (_currentlyLogging)
+ return;
+
+ try
+ {
+ _currentlyLogging = true;
+ var session = @event.Session;
+ // this causes an Autoflush
+ long count = await (session.CreateQuery("select count(o) from Entity o").UniqueResultAsync(cancellationToken));
+ Console.WriteLine("Total entity count: {0}", count);
+ }
+ finally
+ {
+ _currentlyLogging = false;
+ }
+ }
+
+ public void OnPostInsert(PostInsertEvent @event)
+ {
+ if (!(@event.Entity is Entity))
+ return;
+ // This guard is necessary to avoid multiple inserts of the original objects.
+ // Commenting this out is likely to cause one PK violation per run, which seems to be capped to at most 10 attempts.
+ // With the guard, only one PK violation is reported.
+ if (_currentlyLogging)
+ return;
+
+ try
+ {
+ _currentlyLogging = true;
+ var session = @event.Session;
+ // this causes an Autoflush
+ long count = session.CreateQuery("select count(o) from Entity o").UniqueResult();
+ Console.WriteLine("Total entity count: {0}", count);
+ }
+ finally
+ {
+ _currentlyLogging = false;
+ }
+ }
+ }
+ }
+ ///
+ /// Contains generated async methods
+ ///
+ public partial class PostInsertFixture : TestCaseMappingByCode
+ {
+
+ ///
+ /// Contains generated async methods
+ ///
+ private sealed partial class CausesAutoflushListener : IPostInsertEventListener
+ {
+
+ public async Task OnPostInsertAsync(PostInsertEvent @event, CancellationToken cancellationToken)
+ {
+ if (!(@event.Entity is Entity))
+ return;
+ // This guard is necessary to avoid multiple inserts of the original objects.
+ // Commenting this out is likely to cause one PK violation per run, which seems to be capped to at most 10 attempts.
+ // With the guard, only one PK violation is reported.
+ if (_currentlyLogging)
+ return;
+
+ try
+ {
+ _currentlyLogging = true;
+ var session = @event.Session;
+ // this causes an Autoflush
+ long count = await (session.CreateQuery("select count(o) from Entity o").UniqueResultAsync(cancellationToken));
+ Console.WriteLine("Total entity count: {0}", count);
+ }
+ finally
+ {
+ _currentlyLogging = false;
+ }
+ }
+ }
+ }
+}
diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH4077/PostUpdateFixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH4077/PostUpdateFixture.cs
new file mode 100644
index 00000000000..a01c2556c64
--- /dev/null
+++ b/src/NHibernate.Test/Async/NHSpecificTest/NH4077/PostUpdateFixture.cs
@@ -0,0 +1,187 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by AsyncGenerator.
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+
+using System;
+using System.Linq;
+using NHibernate.Cfg;
+using NHibernate.Cfg.MappingSchema;
+using NHibernate.Criterion;
+using NHibernate.Event;
+using NHibernate.Mapping.ByCode;
+using NUnit.Framework;
+
+namespace NHibernate.Test.NHSpecificTest.NH4077
+{
+ using System.Threading.Tasks;
+ using System.Threading;
+ [TestFixture]
+ public partial class PostUpdateFixtureAsync : TestCaseMappingByCode
+ {
+ [Test]
+ public async Task AutoflushInPostUpdateListener_CausesArgumentOutOfRangeException_in_ActionQueueExecuteActionsAsync()
+ {
+ // load a few (more than one) entities and process them. we let NHibernate figure out if they need saving or not.
+ Entity entityOne;
+ Entity entityTwo;
+ using (var session = OpenSession())
+ {
+ entityOne = (await (session.CreateCriteria().Add(Restrictions.Eq(nameof(Entity.Code), "one")).ListAsync())).First();
+ entityTwo = (await (session.CreateCriteria().Add(Restrictions.Eq(nameof(Entity.Code), "two")).ListAsync())).First();
+ }
+
+ // processing omitted (not necessary to illustrate the problem)
+
+ // resave them, but all-or-nothing inside a transaction
+ using (var session = OpenSession())
+ using (var transaction = session.BeginTransaction())
+ {
+ // using FlushMode.Commit prevents the issue; using the default FlushMode.Auto breaks.
+ //session.FlushMode = FlushMode.Commit;
+ await (session.SaveOrUpdateAsync(entityOne));
+ await (session.SaveOrUpdateAsync(entityTwo));
+
+ // committing the transaction causes an ArgumentOutOfRange exception inside ActionQueue.ExecuteActions
+ await (transaction.CommitAsync());
+ await (session.FlushAsync());
+ }
+ }
+
+ protected override HbmMapping GetMappings()
+ {
+ var mapper = new ModelMapper();
+ mapper.Class(rc =>
+ {
+ rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb));
+ rc.Property(x => x.Code);
+ });
+
+ return mapper.CompileMappingForAllExplicitlyAddedEntities();
+ }
+
+ protected override void Configure(Configuration configuration)
+ {
+ base.Configure(configuration);
+ var existingListeners = (configuration.EventListeners.PostUpdateEventListeners ?? new IPostUpdateEventListener[0]).ToList();
+ // this evil listener uses the session to perform a few queries and causes an auto-flush to happen
+ existingListeners.Add(new CausesAutoflushListener());
+ configuration.EventListeners.PostUpdateEventListeners = existingListeners.ToArray();
+ }
+
+ protected override void OnTearDown()
+ {
+ using (var session = OpenSession())
+ using (var transaction = session.BeginTransaction())
+ {
+ session.Delete("from Entity");
+
+ session.Flush();
+ transaction.Commit();
+ }
+ }
+
+ protected override void OnSetUp()
+ {
+ using (var session = OpenSession())
+ using (var transaction = session.BeginTransaction())
+ {
+ // objects must exist before doing the processing; the issue does not occur during
+ session.Save(new Entity { Code = "one" });
+ session.Save(new Entity { Code = "two" });
+
+ session.Flush();
+ transaction.Commit();
+ }
+ }
+
+ private sealed partial class CausesAutoflushListener : IPostUpdateEventListener
+ {
+ private bool _currentlyLogging;
+
+ public async Task OnPostUpdateAsync(PostUpdateEvent @event, CancellationToken cancellationToken)
+ {
+ if (!(@event.Entity is Entity))
+ return;
+ // this guard is necessary to avoid a StackOverflowException due to the Query below triggering this event again.
+ if (_currentlyLogging)
+ return;
+
+ try
+ {
+ _currentlyLogging = true;
+ var session = @event.Session;
+ // this causes an Autoflush
+ long count = await (session.CreateQuery("select count(o) from Entity o").UniqueResultAsync(cancellationToken));
+ Console.WriteLine("Total entity count: {0}", count);
+ }
+ finally
+ {
+ _currentlyLogging = false;
+ }
+ }
+
+ public void OnPostUpdate(PostUpdateEvent @event)
+ {
+ if (!(@event.Entity is Entity))
+ return;
+ // this guard is necessary to avoid a StackOverflowException due to the Query below triggering this event again.
+ if (_currentlyLogging)
+ return;
+
+ try
+ {
+ _currentlyLogging = true;
+ var session = @event.Session;
+ // this causes an Autoflush
+ long count = session.CreateQuery("select count(o) from Entity o").UniqueResult();
+ Console.WriteLine("Total entity count: {0}", count);
+ }
+ finally
+ {
+ _currentlyLogging = false;
+ }
+ }
+ }
+ }
+ ///
+ /// Contains generated async methods
+ ///
+ public partial class PostUpdateFixture : TestCaseMappingByCode
+ {
+
+ ///
+ /// Contains generated async methods
+ ///
+ private sealed partial class CausesAutoflushListener : IPostUpdateEventListener
+ {
+
+ public async Task OnPostUpdateAsync(PostUpdateEvent @event, CancellationToken cancellationToken)
+ {
+ if (!(@event.Entity is Entity))
+ return;
+ // this guard is necessary to avoid a StackOverflowException due to the Query below triggering this event again.
+ if (_currentlyLogging)
+ return;
+
+ try
+ {
+ _currentlyLogging = true;
+ var session = @event.Session;
+ // this causes an Autoflush
+ long count = await (session.CreateQuery("select count(o) from Entity o").UniqueResultAsync(cancellationToken));
+ Console.WriteLine("Total entity count: {0}", count);
+ }
+ finally
+ {
+ _currentlyLogging = false;
+ }
+ }
+ }
+ }
+}
diff --git a/src/NHibernate.Test/NHSpecificTest/NH4077/Model.cs b/src/NHibernate.Test/NHSpecificTest/NH4077/Model.cs
new file mode 100644
index 00000000000..5cbf18b27f4
--- /dev/null
+++ b/src/NHibernate.Test/NHSpecificTest/NH4077/Model.cs
@@ -0,0 +1,10 @@
+using System;
+
+namespace NHibernate.Test.NHSpecificTest.NH4077
+{
+ public class Entity
+ {
+ public virtual Guid Id { get; set; }
+ public virtual string Code { get; set; }
+ }
+}
diff --git a/src/NHibernate.Test/NHSpecificTest/NH4077/PostInsertFixture.cs b/src/NHibernate.Test/NHSpecificTest/NH4077/PostInsertFixture.cs
new file mode 100644
index 00000000000..9e02d22dba4
--- /dev/null
+++ b/src/NHibernate.Test/NHSpecificTest/NH4077/PostInsertFixture.cs
@@ -0,0 +1,112 @@
+using System;
+using System.Linq;
+using NHibernate.Cfg;
+using NHibernate.Cfg.MappingSchema;
+using NHibernate.Event;
+using NHibernate.Mapping.ByCode;
+using NUnit.Framework;
+
+namespace NHibernate.Test.NHSpecificTest.NH4077
+{
+ [TestFixture]
+ public partial class PostInsertFixture : TestCaseMappingByCode
+ {
+ [Test]
+ public void AutoflushInPostInsertListener_CausesDuplicateInserts_WithPrimaryKeyViolations()
+ {
+ using (var session = OpenSession())
+ using (var transaction = session.BeginTransaction())
+ {
+ // using FlushMode.Commit prevents the issue; using the default FlushMode.Auto breaks.
+ //session.FlushMode = FlushMode.Commit;
+ session.Save(new Entity { Code = "one" });
+ session.Save(new Entity { Code = "two" });
+
+ // committing the transaction causes a primary key violation by saving the entities multiple times
+ transaction.Commit();
+ session.Flush();
+ }
+ }
+
+ [Test]
+ public void Autoflush_MayTriggerAdditionalAutoFlushFromEvents()
+ {
+ using (var session = OpenSession())
+ using (var transaction = session.BeginTransaction())
+ {
+ // using FlushMode.Commit prevents the issue; using the default FlushMode.Auto breaks.
+ //session.FlushMode = FlushMode.Commit;
+ session.Save(new Entity { Code = "one" });
+ session.Save(new Entity { Code = "two" });
+
+ // Querying the entity triggers an auto-flush
+ var count = session.CreateQuery("select count(o) from Entity o").UniqueResult();
+ Assert.That(count, Is.GreaterThan(0));
+ transaction.Commit();
+ session.Flush();
+ }
+ }
+
+ protected override HbmMapping GetMappings()
+ {
+ var mapper = new ModelMapper();
+ mapper.Class(rc =>
+ {
+ rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb));
+ rc.Property(x => x.Code);
+ });
+
+ return mapper.CompileMappingForAllExplicitlyAddedEntities();
+ }
+
+ protected override void Configure(Configuration configuration)
+ {
+ base.Configure(configuration);
+ var existingListeners = (configuration.EventListeners.PostInsertEventListeners ?? new IPostInsertEventListener[0]).ToList();
+ // this evil listener uses the session to perform a few queries and causes an auto-flush to happen
+ existingListeners.Add(new CausesAutoflushListener());
+ configuration.EventListeners.PostInsertEventListeners = existingListeners.ToArray();
+ }
+
+ protected override void OnTearDown()
+ {
+ using (var session = OpenSession())
+ using (var transaction = session.BeginTransaction())
+ {
+ session.Delete("from Entity");
+
+ session.Flush();
+ transaction.Commit();
+ }
+ }
+
+ private sealed partial class CausesAutoflushListener : IPostInsertEventListener
+ {
+ private bool _currentlyLogging;
+
+ public void OnPostInsert(PostInsertEvent @event)
+ {
+ if (!(@event.Entity is Entity))
+ return;
+ // This guard is necessary to avoid multiple inserts of the original objects.
+ // Commenting this out is likely to cause one PK violation per run, which seems to be capped to at most 10 attempts.
+ // With the guard, only one PK violation is reported.
+ if (_currentlyLogging)
+ return;
+
+ try
+ {
+ _currentlyLogging = true;
+ var session = @event.Session;
+ // this causes an Autoflush
+ long count = session.CreateQuery("select count(o) from Entity o").UniqueResult();
+ Console.WriteLine("Total entity count: {0}", count);
+ }
+ finally
+ {
+ _currentlyLogging = false;
+ }
+ }
+ }
+ }
+}
diff --git a/src/NHibernate.Test/NHSpecificTest/NH4077/PostUpdateFixture.cs b/src/NHibernate.Test/NHSpecificTest/NH4077/PostUpdateFixture.cs
new file mode 100644
index 00000000000..8e9d9932b83
--- /dev/null
+++ b/src/NHibernate.Test/NHSpecificTest/NH4077/PostUpdateFixture.cs
@@ -0,0 +1,118 @@
+using System;
+using System.Linq;
+using NHibernate.Cfg;
+using NHibernate.Cfg.MappingSchema;
+using NHibernate.Criterion;
+using NHibernate.Event;
+using NHibernate.Mapping.ByCode;
+using NUnit.Framework;
+
+namespace NHibernate.Test.NHSpecificTest.NH4077
+{
+ [TestFixture]
+ public partial class PostUpdateFixture : TestCaseMappingByCode
+ {
+ [Test]
+ public void AutoflushInPostUpdateListener_CausesArgumentOutOfRangeException_in_ActionQueueExecuteActions()
+ {
+ // load a few (more than one) entities and process them. we let NHibernate figure out if they need saving or not.
+ Entity entityOne;
+ Entity entityTwo;
+ using (var session = OpenSession())
+ {
+ entityOne = session.CreateCriteria().Add(Restrictions.Eq(nameof(Entity.Code), "one")).List().First();
+ entityTwo = session.CreateCriteria().Add(Restrictions.Eq(nameof(Entity.Code), "two")).List().First();
+ }
+
+ // processing omitted (not necessary to illustrate the problem)
+
+ // resave them, but all-or-nothing inside a transaction
+ using (var session = OpenSession())
+ using (var transaction = session.BeginTransaction())
+ {
+ // using FlushMode.Commit prevents the issue; using the default FlushMode.Auto breaks.
+ //session.FlushMode = FlushMode.Commit;
+ session.SaveOrUpdate(entityOne);
+ session.SaveOrUpdate(entityTwo);
+
+ // committing the transaction causes an ArgumentOutOfRange exception inside ActionQueue.ExecuteActions
+ transaction.Commit();
+ session.Flush();
+ }
+ }
+
+ protected override HbmMapping GetMappings()
+ {
+ var mapper = new ModelMapper();
+ mapper.Class(rc =>
+ {
+ rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb));
+ rc.Property(x => x.Code);
+ });
+
+ return mapper.CompileMappingForAllExplicitlyAddedEntities();
+ }
+
+ protected override void Configure(Configuration configuration)
+ {
+ base.Configure(configuration);
+ var existingListeners = (configuration.EventListeners.PostUpdateEventListeners ?? new IPostUpdateEventListener[0]).ToList();
+ // this evil listener uses the session to perform a few queries and causes an auto-flush to happen
+ existingListeners.Add(new CausesAutoflushListener());
+ configuration.EventListeners.PostUpdateEventListeners = existingListeners.ToArray();
+ }
+
+ protected override void OnTearDown()
+ {
+ using (var session = OpenSession())
+ using (var transaction = session.BeginTransaction())
+ {
+ session.Delete("from Entity");
+
+ session.Flush();
+ transaction.Commit();
+ }
+ }
+
+ protected override void OnSetUp()
+ {
+ using (var session = OpenSession())
+ using (var transaction = session.BeginTransaction())
+ {
+ // objects must exist before doing the processing; the issue does not occur during
+ session.Save(new Entity { Code = "one" });
+ session.Save(new Entity { Code = "two" });
+
+ session.Flush();
+ transaction.Commit();
+ }
+ }
+
+ private sealed partial class CausesAutoflushListener : IPostUpdateEventListener
+ {
+ private bool _currentlyLogging;
+
+ public void OnPostUpdate(PostUpdateEvent @event)
+ {
+ if (!(@event.Entity is Entity))
+ return;
+ // this guard is necessary to avoid a StackOverflowException due to the Query below triggering this event again.
+ if (_currentlyLogging)
+ return;
+
+ try
+ {
+ _currentlyLogging = true;
+ var session = @event.Session;
+ // this causes an Autoflush
+ long count = session.CreateQuery("select count(o) from Entity o").UniqueResult();
+ Console.WriteLine("Total entity count: {0}", count);
+ }
+ finally
+ {
+ _currentlyLogging = false;
+ }
+ }
+ }
+ }
+}
diff --git a/src/NHibernate/Async/Engine/ActionQueue.cs b/src/NHibernate/Async/Engine/ActionQueue.cs
index 2a0b05e4d86..579cd7ac2a3 100644
--- a/src/NHibernate/Async/Engine/ActionQueue.cs
+++ b/src/NHibernate/Async/Engine/ActionQueue.cs
@@ -40,9 +40,12 @@ public Task AddActionAsync(BulkOperationCleanupAction cleanupAction, Cancellatio
private async Task ExecuteActionsAsync(IList list, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
- int size = list.Count;
- for (int i = 0; i < size; i++)
- await (ExecuteAsync((IExecutable)list[i], cancellationToken)).ConfigureAwait(false);
+ // Actions may raise events to which user code can react and cause changes to action list.
+ // It will then fail here due to list being modified. (Some previous code was dodging the
+ // trouble with a for loop which was not failing provided the list was not getting smaller.
+ // But then it was clearing it without having executed added actions (if any), ...)
+ foreach (IExecutable executable in list)
+ await (ExecuteAsync(executable, cancellationToken)).ConfigureAwait(false);
list.Clear();
await (session.Batcher.ExecuteBatchAsync(cancellationToken)).ConfigureAwait(false);
diff --git a/src/NHibernate/Async/Event/Default/DefaultAutoFlushEventListener.cs b/src/NHibernate/Async/Event/Default/DefaultAutoFlushEventListener.cs
index 8bf233fcd54..864e00e3487 100644
--- a/src/NHibernate/Async/Event/Default/DefaultAutoFlushEventListener.cs
+++ b/src/NHibernate/Async/Event/Default/DefaultAutoFlushEventListener.cs
@@ -36,34 +36,37 @@ public virtual async Task OnAutoFlushAsync(AutoFlushEvent @event, CancellationTo
if (FlushMightBeNeeded(source))
{
- int oldSize = source.ActionQueue.CollectionRemovalsCount;
+ using (source.SuspendAutoFlush())
+ {
+ int oldSize = source.ActionQueue.CollectionRemovalsCount;
- await (FlushEverythingToExecutionsAsync(@event, cancellationToken)).ConfigureAwait(false);
+ await (FlushEverythingToExecutionsAsync(@event, cancellationToken)).ConfigureAwait(false);
- if (FlushIsReallyNeeded(@event, source))
- {
- if (log.IsDebugEnabled)
- log.Debug("Need to execute flush");
+ if (FlushIsReallyNeeded(@event, source))
+ {
+ if (log.IsDebugEnabled)
+ log.Debug("Need to execute flush");
- await (PerformExecutionsAsync(source, cancellationToken)).ConfigureAwait(false);
- PostFlush(source);
- // note: performExecutions() clears all collectionXxxxtion
- // collections (the collection actions) in the session
+ await (PerformExecutionsAsync(source, cancellationToken)).ConfigureAwait(false);
+ PostFlush(source);
+ // note: performExecutions() clears all collectionXxxxtion
+ // collections (the collection actions) in the session
- if (source.Factory.Statistics.IsStatisticsEnabled)
+ if (source.Factory.Statistics.IsStatisticsEnabled)
+ {
+ source.Factory.StatisticsImplementor.Flush();
+ }
+ }
+ else
{
- source.Factory.StatisticsImplementor.Flush();
+
+ if (log.IsDebugEnabled)
+ log.Debug("Dont need to execute flush");
+ source.ActionQueue.ClearFromFlushNeededCheck(oldSize);
}
- }
- else
- {
- if (log.IsDebugEnabled)
- log.Debug("Dont need to execute flush");
- source.ActionQueue.ClearFromFlushNeededCheck(oldSize);
+ @event.FlushRequired = FlushIsReallyNeeded(@event, source);
}
-
- @event.FlushRequired = FlushIsReallyNeeded(@event, source);
}
}
diff --git a/src/NHibernate/Async/Impl/SessionImpl.cs b/src/NHibernate/Async/Impl/SessionImpl.cs
index fe60e80f398..441e6352993 100644
--- a/src/NHibernate/Async/Impl/SessionImpl.cs
+++ b/src/NHibernate/Async/Impl/SessionImpl.cs
@@ -233,25 +233,26 @@ public override async Task ListAsync(IQueryExpression queryExpression, QueryPara
await (AutoFlushIfRequiredAsync(plan.QuerySpaces, cancellationToken)).ConfigureAwait(false);
bool success = false;
- dontFlushFromFind++; //stops flush being called multiple times if this method is recursively called
- try
- {
- await (plan.PerformListAsync(queryParameters, this, results, cancellationToken)).ConfigureAwait(false);
- success = true;
- }
- catch (HibernateException)
- {
- // Do not call Convert on HibernateExceptions
- throw;
- }
- catch (Exception e)
- {
- throw Convert(e, "Could not execute query");
- }
- finally
+ using (SuspendAutoFlush()) //stops flush being called multiple times if this method is recursively called
{
- dontFlushFromFind--;
- await (AfterOperationAsync(success, cancellationToken)).ConfigureAwait(false);
+ try
+ {
+ await (plan.PerformListAsync(queryParameters, this, results, cancellationToken)).ConfigureAwait(false);
+ success = true;
+ }
+ catch (HibernateException)
+ {
+ // Do not call Convert on HibernateExceptions
+ throw;
+ }
+ catch (Exception e)
+ {
+ throw Convert(e, "Could not execute query");
+ }
+ finally
+ {
+ await (AfterOperationAsync(success, cancellationToken)).ConfigureAwait(false);
+ }
}
}
}
@@ -277,15 +278,10 @@ public override async Task> EnumerableAsync(IQueryExpression q
var plan = GetHQLQueryPlan(queryExpression, true);
await (AutoFlushIfRequiredAsync(plan.QuerySpaces, cancellationToken)).ConfigureAwait(false);
- dontFlushFromFind++; //stops flush being called multiple times if this method is recursively called
- try
+ using (SuspendAutoFlush()) //stops flush being called multiple times if this method is recursively called
{
return await (plan.PerformIterateAsync(queryParameters, this, cancellationToken)).ConfigureAwait(false);
}
- finally
- {
- dontFlushFromFind--;
- }
}
}
@@ -299,15 +295,10 @@ public override async Task EnumerableAsync(IQueryExpression queryEx
var plan = GetHQLQueryPlan(queryExpression, true);
await (AutoFlushIfRequiredAsync(plan.QuerySpaces, cancellationToken)).ConfigureAwait(false);
- dontFlushFromFind++; //stops flush being called multiple times if this method is recursively called
- try
+ using (SuspendAutoFlush()) //stops flush being called multiple times if this method is recursively called
{
return await (plan.PerformIterateAsync(queryParameters, this, cancellationToken)).ConfigureAwait(false);
}
- finally
- {
- dontFlushFromFind--;
- }
}
}
@@ -961,10 +952,13 @@ public override async Task