From 639e698ddf6be5e2cf4c44a0bd54004de8b73331 Mon Sep 17 00:00:00 2001 From: Arthur031221 Date: Sat, 17 Oct 2026 01:14:32 +0800 Subject: [PATCH] Map object-typed columns to primitive members again (sql_variant to bool) A column reported as plain object keeps the direct unbox only when the target is itself TypeCode.Object. Primitive and string targets use the flexible convert path, as before the DateOnly/TimeOnly change. --- Dapper/SqlMapper.cs | 8 ++++-- tests/Dapper.Tests/DataReaderTests.cs | 40 ++++++++++++++++++++++++++- 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/Dapper/SqlMapper.cs b/Dapper/SqlMapper.cs index 8f3956cab..502acbfb8 100644 --- a/Dapper/SqlMapper.cs +++ b/Dapper/SqlMapper.cs @@ -3854,10 +3854,12 @@ private static void LoadReaderValueOrBranchToDBNullLabel(ILGenerator il, int ind // note the TypeCode tests are only meaningful for distinct codes: TypeCode.Object // matching TypeCode.Object says nothing (TimeSpan vs TimeOnly?, say), and a direct // unbox there throws; such pairs belong to the flexible-convert path below. A column - // reported as plain *object* (sql_variant etc) keeps the direct unbox: the runtime - // box is the only truth available there + // reported as plain *object* (sql_variant etc) keeps the direct unbox for targets that + // are themselves TypeCode.Object: the runtime box is the only truth available there, + // and a primitive target still needs the conversion (an int box for a bool member) if ((hasTypeHandler = typeHandlers.ContainsKey(unboxType)) || colType == unboxType - || colType == typeof(object) || unboxType == typeof(object) + || (colType == typeof(object) && Type.GetTypeCode(nullUnderlyingType ?? unboxType) == TypeCode.Object) + || unboxType == typeof(object) || (dataTypeCode == unboxTypeCode && dataTypeCode != TypeCode.Object) || (dataTypeCode == Type.GetTypeCode(nullUnderlyingType) && dataTypeCode != TypeCode.Object) || colType == nullUnderlyingType) diff --git a/tests/Dapper.Tests/DataReaderTests.cs b/tests/Dapper.Tests/DataReaderTests.cs index f98bfabed..2cb73edc1 100644 --- a/tests/Dapper.Tests/DataReaderTests.cs +++ b/tests/Dapper.Tests/DataReaderTests.cs @@ -1,4 +1,6 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Data; using System.Data.Common; using System.Linq; using Xunit; @@ -93,6 +95,42 @@ public void TestTreatIntAsABool() Assert.True(SqlMapper.Parse(reader).Single()); } + [Fact] + public void ObjectTypedColumnsConvertToMemberType() + { + // a provider that reports a column as plain object (sql_variant, say) hands back whatever it boxed + var guid = new Guid("5a6ab3c0-7a1e-4f3e-9d34-2d6a1b7e4c11"); + var table = new DataTable(); + table.Columns.Add("Flag", typeof(object)); + table.Columns.Add("NullableFlag", typeof(object)); + table.Columns.Add("Count", typeof(object)); + table.Columns.Add("Id", typeof(object)); + table.Columns.Add("NullableId", typeof(object)); + table.Rows.Add(1, 0, 42, guid, guid); + + var row = table.CreateDataReader().Parse().Single(); + Assert.True(row.Flag); + Assert.False(row.NullableFlag); + Assert.Equal(42L, row.Count); + Assert.Equal(guid, row.Id); + Assert.Equal(guid, row.NullableId); + } + + [Fact] // #2234 + public void SqlVariantIntToBool() + { + Assert.True(connection.QuerySingle("select cast(cast(1 as int) as sql_variant) as Flag").Flag); + } + + private class HazObjectColumns + { + public bool Flag { get; set; } + public bool? NullableFlag { get; set; } + public long Count { get; set; } + public Guid Id { get; set; } + public Guid? NullableId { get; set; } + } + [Fact] public void DiscriminatedUnion_IDataReader() {