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.cs → QueryRowImpl<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
Summary
The
finallyblock inQueryRowImpl<T>callscmd.Cancel()when the reader isnot 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.cs→QueryRowImpl<T>Root Cause
cmd.Cancel()is reached wheneverreaderis not disposed before the finallyblock. This happens on all non-happy paths, including:
ThrowZeroRows()calledCancel()firedThrowMultipleRows()calledCancel()firedCancel()firedOn 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
cmd.Cancel()→ silently acknowledged, resources releasedcmd.Cancel()→ raisesORA-1013, surfaces as an unrelated errorin 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:Though the provider-agnostic approach (simply removing the call) is cleaner since
Dispose()is sufficient.Environment
mainbranch)Oracle.ManagedDataAccess.CoreReferences