DapperLib/Dapper · error · NotSupportedException

ValueTuple should not be used for parameters - the language-

Error message

ValueTuple should not be used for parameters - the language-level names are not available to use as parameter names, and it adds unnecessary boxing

What it means

Thrown by CreateParamInfoGenerator when the type of the `param` object is a ValueTuple (e.g. `(int Id, string Name)`). Dapper reads parameter names via reflection, but ValueTuple field names (Item1/Item2 or the language names) are not stored as real metadata at runtime, so it cannot bind them reliably; it also adds boxing overhead. The check IsValueTuple (SqlMapper.cs:2557) detects any `System.ValueTuple<...>` (including Nullable wrappers) and rejects it.

Source

Thrown at Dapper/SqlMapper.cs:2562

        /// Internal use only.
        /// </summary>
        /// <param name="identity">The identity of the generator.</param>
        /// <param name="checkForDuplicates">Whether to check for duplicates.</param>
        /// <param name="removeUnused">Whether to remove unused parameters.</param>
        public static Action<IDbCommand, object> CreateParamInfoGenerator(Identity identity, bool checkForDuplicates, bool removeUnused) =>
            CreateParamInfoGenerator(identity, checkForDuplicates, removeUnused, GetLiteralTokens(identity.Sql));

        private static bool IsValueTuple(Type? type) => (type?.IsValueType == true
                                                       && type.FullName?.StartsWith("System.ValueTuple`", StringComparison.Ordinal) == true)
                                                       || (type is not null && IsValueTuple(Nullable.GetUnderlyingType(type)));

        internal static Action<IDbCommand, object?> CreateParamInfoGenerator(Identity identity, bool checkForDuplicates, bool removeUnused, IList<LiteralToken> literals)
        {
            Type type = identity.ParametersType!;

            if (IsValueTuple(type))
            {
                throw new NotSupportedException("ValueTuple should not be used for parameters - the language-level names are not available to use as parameter names, and it adds unnecessary boxing");
            }

            bool filterParams = removeUnused && identity.CommandType.GetValueOrDefault(CommandType.Text) == CommandType.Text;
            
            if (filterParams && Settings.SupportLegacyParameterTokens)
            {
                filterParams = !CompiledRegex.LegacyParameter.IsMatch(identity.Sql);
            }
            
            var dm = new DynamicMethod("ParamInfo" + Guid.NewGuid().ToString(), null, [typeof(IDbCommand), typeof(object)], type, true);

            var il = dm.GetILGenerator();

            bool isStruct = type.IsValueType;
            var _sizeLocal = (LocalBuilder?)null;
            LocalBuilder GetSizeLocal() => _sizeLocal ??= il.DeclareLocal(typeof(int));
            il.Emit(OpCodes.Ldarg_1); // stack is now [untyped-param]

View on GitHub (pinned to 72a54c475f)

Solutions

  1. Use an anonymous type instead: `new { Id = 1, Name = "x" }` — anonymous-type member names ARE available to Dapper.
  2. Use a small named class or record for the parameters.
  3. Use DynamicParameters to add parameters explicitly by name.
  4. If you received a tuple, project it into an anonymous object before passing to Dapper.

Example fix

// before
var rows = cnn.Query<T>("select * from t where id = @Id", (Id: 1));
// after
var rows = cnn.Query<T>("select * from t where id = @Id", new { Id = 1 });
Defensive patterns

Strategy: validation

Validate before calling

// Convert tuples to anonymous objects before passing to Dapper.
var p = new { Id = tuple.Id, Name = tuple.Name };
var rows = cnn.Query<T>(sql, p);

Type guard

static bool IsValueTupleParam(object? p) => p?.GetType().FullName?.StartsWith("System.ValueTuple`", StringComparison.Ordinal) == true;

Prevention

When it happens

Trigger: Passing a ValueTuple as the parameter object to any Dapper method: `cnn.Query<T>(sql, (Id: 1, Name: "x"))`. Even though C# lets you name the fields, those names are erased at runtime, so Dapper cannot map them to `@Id`/`@Name`.

Common situations: Using tuple syntax for ad-hoc parameters; returning a tuple from a helper and feeding it straight to Dapper; migrating from anonymous types to tuples for brevity.

Related errors


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