DapperLib/Dapper · error · ArgumentException

you must provide at least one type to deserialize

Error message

you must provide at least one type to deserialize

What it means

MultiMapAsync (the multi-mapping async path, reached via QueryAsync<TReturn>(sql, Type[] types, map, ...)) throws ArgumentException("you must provide at least one type to deserialize") when the types array has zero elements. Multi-mapping needs at least one source type to split the result set on; an empty array is unrecoverable. Checked after the CommandDefinition is built, before any connection work.

Source

Thrown at Dapper/SqlMapper.Async.cs:979

        /// <param name="param">The parameters to use for this query.</param>
        /// <param name="transaction">The transaction to use for this query.</param>
        /// <param name="buffered">Whether to buffer the results in memory.</param>
        /// <param name="splitOn">The field we should split and read the second object from (default: "Id").</param>
        /// <param name="commandTimeout">Number of seconds before command execution timeout.</param>
        /// <param name="commandType">Is it a stored proc or a batch?</param>
        /// <returns>An enumerable of <typeparamref name="TReturn"/>.</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")]
        public static Task<IEnumerable<TReturn>> QueryAsync<TReturn>(this IDbConnection cnn, string sql, Type[] types, Func<object[], TReturn> map, object? param = null, IDbTransaction? transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null)
        {
            var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default);
            return MultiMapAsync(cnn, command, types, map, splitOn);
        }

        private static async Task<IEnumerable<TReturn>> MultiMapAsync<TReturn>(this IDbConnection cnn, CommandDefinition command, Type[] types, Func<object[], TReturn> map, string splitOn)
        {
            if (types.Length < 1)
            {
                throw new ArgumentException("you must provide at least one type to deserialize");
            }

            object? param = command.Parameters;
            var identity = new IdentityWithTypes(command.CommandText, command.CommandTypeDirect, cnn, types[0], param?.GetType(), types);
            var info = GetCacheInfo(identity, param, command.AddToCache);
            bool wasClosed = cnn.State == ConnectionState.Closed;
            try
            {
                if (wasClosed) await cnn.TryOpenAsync(command.CancellationToken).ConfigureAwait(false);
                using var cmd = command.TrySetupAsyncCommand(cnn, info.ParamReader);
                using var reader = await ExecuteReaderWithFlagsFallbackAsync(cmd, wasClosed, CommandBehavior.SequentialAccess | CommandBehavior.SingleResult, command.CancellationToken).ConfigureAwait(false);
                var results = MultiMapImpl(null, default, types, map, splitOn, reader, identity, true);
                return command.Buffered ? results.ToList() : results;
            }
            finally
            {
                if (wasClosed) cnn.Close();
            }

View on GitHub (pinned to 72a54c475f)

Solutions

  1. Ensure the types array contains at least the primary type before calling the multi-map overload.
  2. Validate the dynamically built types array length and throw a clearer upstream error if empty.

Example fix

// before
var types = Array.Empty<Type>();
var rows = await cnn.QueryAsync<Foo>(sql, types, map);

// after
var types = new[] { typeof(A), typeof(B) };
var rows = await cnn.QueryAsync<Foo>(sql, types, map);
Defensive patterns

Strategy: validation

Validate before calling

if (types is null || types.Length < 1) throw new ArgumentException("At least one type is required for multi-mapping.");
var rows = await cnn.QueryAsync<TReturn>(sql, types, map);

Try / catch

try { await cnn.QueryAsync<TReturn>(sql, types, map); }
catch (ArgumentException ex) when (ex.Message.Contains("at least one type"))
{ /* populate the types array with the primary type and retry */ }

Prevention

When it happens

Trigger: Calling the multi-map QueryAsync overload with an empty Type[] types array (e.g. types built dynamically from a list that came back empty).

Common situations: Constructing the types array from config/metadata where the source list was empty; programmatically filtering types down to zero; a refactor that dropped the primary type from the array.

Related errors


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