-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
Description
Description
A change introduced in version 2.1.66 to cancel the command in a finally block (https://github.com/DapperLib/Dapper/blob/main/Dapper/SqlMapper.Async.cs#L1336) causes significant performance degradation when used with the Npgsql provider (PostgreSQL).
The issue is that for NpgsqlDataReader, the reader.IsClosed property returns false even after all data has been completely read and the reader is functionally finished. This triggers the explicit cmd.Cancel() in the finally block unnecessarily.
For PostgreSQL, canceling a command is not a lightweight operation. It requires opening a new physical connection to the server to send the cancellation request. This leads to:
- Performance overhead on the application side due to the extra connection setup.
- Performance overhead on the database server, which has to process the cancellation command and abort the already-finished query.
- Connection pool churn, as the cancellation process uses up additional connections temporarily.
This behavior negates the performance benefits of using Unbuffered asynchronous reads.
Proposed Solution
The cancellation logic should be more intelligent. Instead of relying solely on !reader.IsClosed, it should also consider if the reader has already consumed the entire result set naturally.
A better condition might be to check !reader.IsClosed && !reader.NextResult(). Alternatively, a more sophisticated approach could be taken to determine if the reader has been fully enumerated without being explicitly closed.
Steps to Reproduce
- Use Dapper 2.1.66+ with Npgsql.
- Execute a
QueryUnbufferedAsyncquery. - Observe in SQL logs or with a profiler (like
log_statement = 'all'inpostgresql.conf) that aCANCELcommand is issued for the query even after it has completed successfully.
This behavior is specific to how the Npgsql provider works. Other providers (e.g., SQL Server) may correctly set IsClosed to true after full enumeration, making the Cancel() call a no-op and avoiding this performance issue.