Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions src/NHibernate.Test/Ado/BatcherFixture.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Linq;
using NHibernate.AdoNet;
using NHibernate.Cfg;
using NUnit.Framework;
Expand Down Expand Up @@ -303,5 +304,46 @@ public void AbstractBatcherLogFormattedSql()
Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(1));
Cleanup();
}

[Test]
[Description("The batcher should handle empty batch execution without throwing exceptions.")]
public void EmptyBatchShouldNotThrowException()
{
// This test verifies that batchers handle empty batches correctly
// DbBatchBatcher had a bug where ExecuteBatch was called on an empty batch,
// causing InvalidOperationException: CommandText property has not been initialized
// See GH-3725

using var session = OpenSession();
using var transaction = session.BeginTransaction();

// Execute queries that don't add to the batch
_ = session.Query<VerySimple>().FirstOrDefault();

// Prepare a new command which triggers ExecuteBatch on any existing batch
// If the previous command didn't add anything to the batch, this would fail
// before the fix with InvalidOperationException
_ = session.Query<VerySimple>().FirstOrDefault();

// Test passes if no exception is thrown
transaction.Commit();
}

[Test]
[Description("Flush with no pending operations should handle empty batch correctly.")]
public void FlushEmptyBatchShouldNotThrowException()
{
using var session = OpenSession();
using var transaction = session.BeginTransaction();

// Query without any modifications
var count = session.Query<VerySimple>().Count();
Assert.That(count, Is.GreaterThanOrEqualTo(0));

// Flush with no pending batch operations should not throw
Assert.DoesNotThrow(() => session.Flush());

transaction.Commit();
}
}
}
43 changes: 43 additions & 0 deletions src/NHibernate.Test/Async/Ado/BatcherFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@
//------------------------------------------------------------------------------


using System.Linq;
using NHibernate.AdoNet;
using NHibernate.Cfg;
using NUnit.Framework;
using NHibernate.Linq;

namespace NHibernate.Test.Ado
{
Expand Down Expand Up @@ -275,5 +277,46 @@ public async Task AbstractBatcherLogFormattedSqlAsync()
Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(1));
await (CleanupAsync());
}

[Test]
[Description("The batcher should handle empty batch execution without throwing exceptions.")]
public async Task EmptyBatchShouldNotThrowExceptionAsync()
{
// This test verifies that batchers handle empty batches correctly
// DbBatchBatcher had a bug where ExecuteBatch was called on an empty batch,
// causing InvalidOperationException: CommandText property has not been initialized
// See GH-3725

using var session = OpenSession();
using var transaction = session.BeginTransaction();

// Execute queries that don't add to the batch
_ = await (session.Query<VerySimple>().FirstOrDefaultAsync());

// Prepare a new command which triggers ExecuteBatch on any existing batch
// If the previous command didn't add anything to the batch, this would fail
// before the fix with InvalidOperationException
_ = await (session.Query<VerySimple>().FirstOrDefaultAsync());

// Test passes if no exception is thrown
await (transaction.CommitAsync());
}

[Test]
[Description("Flush with no pending operations should handle empty batch correctly.")]
public async Task FlushEmptyBatchShouldNotThrowExceptionAsync()
{
using var session = OpenSession();
using var transaction = session.BeginTransaction();

// Query without any modifications
var count = await (session.Query<VerySimple>().CountAsync());
Assert.That(count, Is.GreaterThanOrEqualTo(0));

// Flush with no pending batch operations should not throw
Assert.DoesNotThrowAsync(() => session.FlushAsync());

await (transaction.CommitAsync());
}
}
}
12 changes: 12 additions & 0 deletions src/NHibernate/AdoNet/DbBatchBatcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,12 @@ public override Task AddToBatchAsync(IExpectation expectation, CancellationToken

protected override void DoExecuteBatch(DbCommand ps)
{
if (_currentBatch.BatchCommands.Count == 0)
{
Expectations.VerifyOutcomeBatched(_totalExpectedRowsAffected, 0, ps);
return;
}

try
{
Log.Debug("Executing batch");
Expand Down Expand Up @@ -145,6 +151,12 @@ protected override void DoExecuteBatch(DbCommand ps)
protected override async Task DoExecuteBatchAsync(DbCommand ps, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
if (_currentBatch.BatchCommands.Count == 0)
{
Expectations.VerifyOutcomeBatched(_totalExpectedRowsAffected, 0, ps);
return;
}

try
{
Log.Debug("Executing batch");
Expand Down