DapperLib / DapperLib/DapperAOT

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

Open
#226 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
C#
Stars
472
Forks
43
Avg merge
1d 4h
Merged PRs (30d)
18

Description

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.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the generator path that emits SqlMapper.PackListParameters and the UpdateParameters handling for byte[] members, comparing it with the shown 1.0.52 scalar-parameter output. Reproduce the INSERT against Microsoft.Data.Sqlite, then verify byte[] values are bound as one binary parameter for inserts and comparisons, including empty and large arrays.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp
Domain
databases
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
72/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.