From 98104acbe4783eef6dfbf3186632e8a7ca5e8509 Mon Sep 17 00:00:00 2001 From: MoonsvnLyn <287222957+FirmaSpring@users.noreply.github.com> Date: Mon, 17 Aug 2026 18:02:09 +0800 Subject: [PATCH] Preserve Size set by type handlers for string parameters When an anonymous object parameter goes through the IL generator path, the generated code set the parameter Size after invoking the registered type handler's SetValue, overwriting whatever Size the handler had configured (typically resetting it to 4000 or -1). Move the string Size emission into the non-handler branch so the handler owns the parameter configuration, matching what DynamicParameters already does. Fixes #2141 Co-authored-by: FirmamentalSpring <287222957+FirmaSpring@users.noreply.github.com> --- Dapper/SqlMapper.cs | 24 ++++++------ tests/Dapper.Tests/Providers/SqliteTests.cs | 41 +++++++++++++++++++++ 2 files changed, 53 insertions(+), 12 deletions(-) diff --git a/Dapper/SqlMapper.cs b/Dapper/SqlMapper.cs index 2fa0e72b7..a08dd19a9 100644 --- a/Dapper/SqlMapper.cs +++ b/Dapper/SqlMapper.cs @@ -2852,21 +2852,21 @@ private static bool IsValueTuple(Type? type) => (type?.IsValueType == true else { il.EmitCall(OpCodes.Callvirt, typeof(IDataParameter).GetProperty(nameof(IDataParameter.Value))!.GetSetMethod()!, null);// stack is now [parameters] [[parameters]] [parameter] - } - if (prop.PropertyType == typeof(string)) - { - var endOfSize = il.DefineLabel(); - var sizeLocal = GetSizeLocal(); - // don't set if 0 - il.Emit(OpCodes.Ldloc, sizeLocal); // [parameters] [[parameters]] [parameter] [size] - il.Emit(OpCodes.Brfalse_S, endOfSize); // [parameters] [[parameters]] [parameter] + if (prop.PropertyType == typeof(string)) + { + var endOfSize = il.DefineLabel(); + var sizeLocal = GetSizeLocal(); + // don't set if 0 + il.Emit(OpCodes.Ldloc, sizeLocal); // [parameters] [[parameters]] [parameter] [size] + il.Emit(OpCodes.Brfalse_S, endOfSize); // [parameters] [[parameters]] [parameter] - il.Emit(OpCodes.Dup);// stack is now [parameters] [[parameters]] [parameter] [parameter] - il.Emit(OpCodes.Ldloc, sizeLocal); // stack is now [parameters] [[parameters]] [parameter] [parameter] [size] - il.EmitCall(OpCodes.Callvirt, typeof(IDbDataParameter).GetProperty(nameof(IDbDataParameter.Size))!.GetSetMethod()!, null); // stack is now [parameters] [[parameters]] [parameter] + il.Emit(OpCodes.Dup);// stack is now [parameters] [[parameters]] [parameter] [parameter] + il.Emit(OpCodes.Ldloc, sizeLocal); // stack is now [parameters] [[parameters]] [parameter] [parameter] [size] + il.EmitCall(OpCodes.Callvirt, typeof(IDbDataParameter).GetProperty(nameof(IDbDataParameter.Size))!.GetSetMethod()!, null); // stack is now [parameters] [[parameters]] [parameter] - il.MarkLabel(endOfSize); + il.MarkLabel(endOfSize); + } } if (checkForDuplicates) { diff --git a/tests/Dapper.Tests/Providers/SqliteTests.cs b/tests/Dapper.Tests/Providers/SqliteTests.cs index b0c5e0f1b..553f2cad7 100644 --- a/tests/Dapper.Tests/Providers/SqliteTests.cs +++ b/tests/Dapper.Tests/Providers/SqliteTests.cs @@ -1,5 +1,6 @@ using Microsoft.Data.Sqlite; using System; +using System.Data; using System.Data.Common; using System.Linq; using System.Threading; @@ -65,6 +66,46 @@ public void Issue466_SqliteHatesOptimizations() Assert.Equal(42, row.Id); } + [FactSqlite] + public void TypeHandlerSetValue_StringParameterSizeIsNotOverwritten_Issue2141() + { + using var connection = GetSQLiteConnection(); + + SqlMapper.ResetTypeHandlers(); + SqlMapper.RemoveTypeMap(typeof(string)); + var handler = new SizedStringTypeHandler(); + SqlMapper.AddTypeHandler(handler); + try + { + // anonymous object parameters take the IL generator path, where the + // generated code used to overwrite the Size set by the handler + connection.Execute("select @p", new { p = "abc" }); + + Assert.NotNull(handler.LastParameter); + Assert.Equal(SizedStringTypeHandler.ExpectedSize, handler.LastParameter.Size); + } + finally + { + SqlMapper.ResetTypeHandlers(); + SqlMapper.AddTypeMap(typeof(string), DbType.String); + } + } + + private sealed class SizedStringTypeHandler : SqlMapper.TypeHandler + { + public const int ExpectedSize = 12345; + public IDbDataParameter? LastParameter { get; private set; } + + public override void SetValue(IDbDataParameter parameter, string? value) + { + parameter.Value = value; + parameter.Size = ExpectedSize; + LastParameter = parameter; + } + + public override string Parse(object value) => (string)value; + } + [FactSqlite] public async Task Issue466_SqliteHatesOptimizations_Async() {