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.
Summary
Since 1.1.0 (list expansion, #197), the generator treats a
byte[]parameter member as an expandable list and emitsSqlMapper.PackListParametersfor it. In 1.0.52 the same member was bound as a single parameter, and vanilla Dapper has always boundbyte[]asDbType.Binaryand 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>.4SqliteException: 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:
1.1.0:
UpdateParametersalso drops the member in 1.1.0.Expected
byte[](and, to match vanilla's type map, anything else vanilla maps to a scalarDbTypebefore it considersIEnumerable) 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 runtimebyte[]as one BLOB: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.