nhibernate-core icon indicating copy to clipboard operation
nhibernate-core copied to clipboard

DbBatchBatcher may fail on empty batch execution

Open ja-kar opened this issue 1 month ago • 0 comments

Description

DbBatchBatcher (available in NHibernate 5.6.0) throws InvalidOperationException when attempting to execute an empty batch, while other batchers (GenericBatchingBatcher and SqlClientBatchingBatcher) handle this scenario correctly.

Error

System.InvalidOperationException: ExecuteNonQuery: CommandText property has not been initialized at Microsoft.Data.SqlClient.SqlCommand.ValidateCommand(Boolean isAsync, String method) at Microsoft.Data.SqlClient.SqlCommand.InternalExecuteNonQuery(TaskCompletionSource`1 completion, Boolean sendToPipe, Int32 timeout, Boolean& usedCache, Boolean asyncWrite, Boolean inRetry, String methodName) at Microsoft.Data.SqlClient.SqlCommand.ExecuteNonQuery() at Microsoft.Data.SqlClient.SqlBatch.ExecuteNonQuery() at NHibernate.AdoNet.DbBatchBatcher.DoExecuteBatch(DbCommand ps)

Root Cause

DbBatchBatcher.DoExecuteBatch() and DoExecuteBatchAsync() lack empty batch validation that exists in other batchers. When ExecuteBatch() is called with an empty batch (_currentBatch.BatchCommands.Count == 0), the code attempts to execute a DbBatch with no commands, causing the CommandText to be uninitialized.

File: src/NHibernate/AdoNet/DbBatchBatcher.cs Methods: DoExecuteBatch() (line 116) and DoExecuteBatchAsync() (line 145)

Comparison with Other Batchers

GenericBatchingBatcher (working correctly):

protected override void DoExecuteBatch(DbCommand ps)
{
    if (_currentBatch.CountOfCommands == 0)
    {
        Expectations.VerifyOutcomeBatched(_totalExpectedRowsAffected, 0, ps);
        return;
    }
    // ... proceed with execution
}

DbBatchBatcher (missing check):

protected override void DoExecuteBatch(DbCommand ps)
{
    try
    {
        // Missing empty batch check here
        rowsAffected = _currentBatch.ExecuteNonQuery(); // Throws on empty batch

Proposed Fix

Add empty batch check at the beginning of both methods:

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

Environment

  • NHibernate version: 5.6.0
  • .NET version: 8
  • Database: SQL Server

Impact

This causes runtime exceptions in scenarios where ExecuteBatch() is triggered without any commands being added to the batch, which can occur during normal session lifecycle operations.

ja-kar avatar Nov 17 '25 10:11 ja-kar