DapperLib/Dapper · error · InvalidOperationException

Async operations require use of a DbConnection or an IDbConn

Error message

Async operations require use of a DbConnection or an IDbConnection where .CreateCommand() returns a DbCommand

What it means

TrySetupAsyncCommand throws InvalidOperationException("Async operations require use of a DbConnection or an IDbConnection where .CreateCommand() returns a DbCommand") when the command built via SetupCommand is not a DbCommand. Dapper's async execution needs DbCommand for ExecuteReaderAsync etc.; a connection whose CreateCommand yields a plain IDbCommand cannot drive async I/O. The message is an intentional, clearer replacement for an InvalidCastException.

Source

Thrown at Dapper/SqlMapper.Async.cs:418

            }
            else
            {
                throw new InvalidOperationException("Async operations require use of a DbConnection or an already-open IDbConnection");
            }
        }

        /// <summary>
        /// Attempts setup a <see cref="DbCommand"/> on a <see cref="DbConnection"/>, with a better error message for unsupported usages.
        /// </summary>
        private static DbCommand TrySetupAsyncCommand(this CommandDefinition command, IDbConnection cnn, Action<IDbCommand, object?>? paramReader)
        {
            if (command.SetupCommand(cnn, paramReader) is DbCommand dbCommand)
            {
                return dbCommand;
            }
            else
            {
                throw new InvalidOperationException("Async operations require use of a DbConnection or an IDbConnection where .CreateCommand() returns a DbCommand");
            }
        }

        private static async Task<IEnumerable<T>> QueryAsync<T>(this IDbConnection cnn, Type effectiveType, CommandDefinition command)
        {
            object? param = command.Parameters;
            var identity = new Identity(command.CommandText, command.CommandTypeDirect, cnn, effectiveType, param?.GetType());
            var info = GetCacheInfo(identity, param, command.AddToCache);
            bool wasClosed = cnn.State == ConnectionState.Closed;
            var cancel = command.CancellationToken;
            using var cmd = command.TrySetupAsyncCommand(cnn, info.ParamReader);
            DbDataReader? reader = null;
            try
            {
                if (wasClosed) await cnn.TryOpenAsync(cancel).ConfigureAwait(false);
                reader = await ExecuteReaderWithFlagsFallbackAsync(cmd, wasClosed, CommandBehavior.SequentialAccess | CommandBehavior.SingleResult, cancel).ConfigureAwait(false);

                var tuple = info.Deserializer;

View on GitHub (pinned to 72a54c475f)

Solutions

  1. Use a standard provider whose command derives from System.Data.Common.DbCommand (all mainstream providers do).
  2. In tests, mock DbConnection/DbCommand rather than the raw IDb* interfaces, or use an in-memory provider like Microsoft.Data.Sqlite.

Example fix

// before (test mock)
var mockConn = Substitute.For<IDbConnection>();
mockConn.CreateCommand().Returns(mockCmd); // mockCmd is IDbCommand
await mockConn.QueryAsync<T>(sql); // throws

// after
var mockConn = Substitute.For<DbConnection>();
// provide DbCommand-derived mock; or use a real in-memory provider
Defensive patterns

Strategy: type-guard

Validate before calling

if (cnn is not System.Data.Common.DbConnection || cnn.CreateCommand() is not System.Data.Common.DbCommand)
    throw new InvalidOperationException("Async Dapper requires a connection whose CreateCommand returns a DbCommand.");

Type guard

static bool HasDbCommand(IDbConnection c) => c.CreateCommand() is System.Data.Common.DbCommand;

Try / catch

try { await cnn.QueryAsync<T>(sql); }
catch (InvalidOperationException ex) when (ex.Message.Contains("returns a DbCommand"))
{ /* use a provider whose command derives from DbCommand */ }

Prevention

When it happens

Trigger: Using an async Dapper API with a connection whose CreateCommand() returns a custom IDbCommand that is not a DbCommand (legacy provider or a mock IDbConnection.CreateCommand returning a stub IDbCommand).

Common situations: Unit tests mocking IDbConnection.CreateCommand to return a non-DbCommand stub; a legacy ADO.NET provider that does not derive its command from DbConnection/DbCommand.

Related errors


AI-assisted analysis of DapperLib/Dapper@72a54c475f (2026-08-13). Data as JSON: /api/errors/95ef9b5e80f4be0f. Report an issue: GitHub.