DapperLib / DapperLib/Dapper

cmd.Cancel() in QueryRowImpl finally block causes ORA-1013 on Oracle Database Queries

Open
#2,215 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
C#
Stars
18.4k
Forks
3.7k
Avg merge
5h 8m
Merged PRs (30d)
1

Description

Summary

The finally block in QueryRowImpl<T> calls cmd.Cancel() when the reader is
not null and not closed. While this works safely with SQL Server, it causes Oracle
to raise ORA-1013 ("user requested cancel of current operation") on any
non-happy-path exit from the method.

Affected Method

SqlMapper.csQueryRowImpl<T>

finally
{
    if (reader is not null)
    {
        if (!reader.IsClosed)
        {
            try { cmd?.Cancel(); }
            catch { /* don't spoil any existing exception */ }
        }
        reader.Dispose();
    }
    if (wasClosed) cnn.Close();
    cmd?.Parameters.Clear();
    cmd?.Dispose();
}

Root Cause

cmd.Cancel() is reached whenever reader is not disposed before the finally
block. This happens on all non-happy paths, including:

Scenario reader null before finally?
Happy path (rows found, disposed) ✅ Yes — safe
ThrowZeroRows() called ❌ No — Cancel() fired
ThrowMultipleRows() called ❌ No — Cancel() fired
Any exception thrown mid-read ❌ No — Cancel() fired

On SQL Server, cmd.Cancel() is a safe and common cleanup pattern.
On Oracle (ODP.NET / Oracle.ManagedDataAccess), it sends an explicit cancel
signal to the server, which raises ORA-1013 as a hard error — masking the
original exception (e.g. "no rows found", "multiple rows", or a query error).

Behaviour Discrepancy

  • SQL Server: cmd.Cancel() → silently acknowledged, resources released
  • Oracle: cmd.Cancel() → raises ORA-1013, surfaces as an unrelated error
    in the application

Workaround

Commenting out the cmd.Cancel() call resolves the issue. reader.Dispose()
alone is sufficient for cleanup on Oracle — the explicit cancel is unnecessary
and harmful.

Suggested Fix

Consider making the cancel behaviour conditional on the provider, or removing it
entirely since reader.Dispose() already handles cleanup across all major providers:

//if (!reader.IsClosed)
//{
    // cmd.Cancel() triggers ORA-1013 on Oracle (see issue #XXXX).
    // reader.Dispose() below is sufficient for cleanup across providers.
    // try { cmd?.Cancel(); }
    // catch { /* don't spoil any existing exception */ }
//}

Though the provider-agnostic approach (simply removing the call) is cleaner since
Dispose() is sufficient.

Environment

  • Dapper version: latest (main branch)
  • Oracle driver: Oracle.ManagedDataAccess.Core
  • .NET version: .NET 10
  • Database: Oracle

References

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in SqlMapper.cs at QueryRowImpl and inspect the finally block's reader cleanup and cmd.Cancel() path. Reproduce the zero-row, multiple-row, or mid-read exception cases with Oracle, then verify that the original error is preserved, reader cleanup still occurs, and SQL Server behavior is not regressed.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp, sql
Domain
database
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.