Skip to content

1.1.0 regression: a byte[] parameter member is list-expanded instead of bound as one binary parameter #226

Description

@gathda

Summary

Since 1.1.0 (list expansion, #197), the generator treats a byte[] parameter member as an expandable list and emits SqlMapper.PackListParameters for it. In 1.0.52 the same member was bound as a single parameter, and vanilla Dapper has always bound byte[] as DbType.Binary and never expanded it. Every statement that writes or compares a BLOB through an anonymous or typed parameter object now fails at run time, with no build diagnostic.

Reproduction

net10.0, Dapper 2.1.86, Microsoft.Data.Sqlite 10.0.x, <InterceptorsNamespaces>$(InterceptorsNamespaces);Dapper.AOT</InterceptorsNamespaces>.

using Dapper;
using Microsoft.Data.Sqlite;

[module: DapperAot]

using var connection = new SqliteConnection("Data Source=:memory:");
connection.Open();
connection.Execute("CREATE TABLE blobs (id INTEGER PRIMARY KEY, data BLOB NOT NULL)");

byte[] payload = [1, 2, 3, 4];
connection.Execute("INSERT INTO blobs (id, data) VALUES (@Id, @Data)", new { Id = 1, Data = payload });
Console.WriteLine(connection.QuerySingle<long>("SELECT length(data) FROM blobs WHERE id = 1"));
Dapper.AOT Result
1.0.52 prints 4
1.1.0 SqliteException: SQLite Error 1: 'row value misused'

The SQL is rewritten to VALUES (@Id, (@Data1,@Data2,@Data3,@Data4)). Other shapes we hit in real code: an empty array in a comparison (WHERE name_bytes > @After) silently returns no rows, and a large blob fails with too many SQL variables.

Generated code

1.0.52:

p = cmd.CreateParameter();
p.ParameterName = "Data";
p.Direction = global::System.Data.ParameterDirection.Input;
p.Value = AsValue(typed.Data);
ps.Add(p);

1.1.0:

#pragma warning disable CS0618 // list-expansion: this *is* the library usage
global::Dapper.SqlMapper.PackListParameters(cmd.Command!, "Data", typed.Data);
#pragma warning restore CS0618

UpdateParameters also drops the member in 1.1.0.

Expected

byte[] (and, to match vanilla's type map, anything else vanilla maps to a scalar DbType before it considers IEnumerable) is bound as a single parameter, as in 1.0.52 and in vanilla Dapper.

Workaround

Give the member the static type object, which takes the scalar path; the provider then binds the runtime byte[] as one BLOB:

new { Id = 1, Data = (object)payload }

Impact

Found when upgrading four applications from 1.0.52: all BLOB writes failed (pictures, sealed payloads, attachment chunks) and one keyset-pagination query returned nothing. Existing tests caught it; nothing at build time did. A DAP diagnostic would not be the right fix, since this is ordinary, valid Dapper usage.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions