From 3f540b5c844618de53ddcd943661a6a28d71e8ec Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sat, 11 Jul 2026 16:49:49 +0100 Subject: [PATCH] Correct DateOnly transport as sql_variant fields in TVPs Instances of DateOnly stored within a SqlDataRecord field of type Variant will now be sent as dates, not datetimes. By extension: sending DateOnly instances with values outside the acceptable range for a datetime will no longer throw overflow exceptions. --- .../Data/SqlClient/Server/SqlRecordBuffer.cs | 2 +- .../Data/SqlClient/Server/ValueUtilsSmi.cs | 13 ++++++++++--- .../SQL/ParameterTest/DateTimeVariantTests.cs | 19 +++---------------- 3 files changed, 14 insertions(+), 20 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SqlRecordBuffer.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SqlRecordBuffer.cs index d8e9dc94c6..9d88a8dd5e 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SqlRecordBuffer.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SqlRecordBuffer.cs @@ -463,7 +463,7 @@ internal SmiMetaData VariantType } set { - Debug.Assert(value != null && (value.SqlDbType == SqlDbType.Money || value.SqlDbType == SqlDbType.NVarChar), + Debug.Assert(value != null && (value.SqlDbType == SqlDbType.Money || value.SqlDbType == SqlDbType.NVarChar || value.SqlDbType == SqlDbType.Date), "Invalid metadata"); _metadata = value; diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/ValueUtilsSmi.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/ValueUtilsSmi.cs index 94338c8dfb..f6e49d0111 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/ValueUtilsSmi.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/ValueUtilsSmi.cs @@ -1044,6 +1044,10 @@ internal static object GetSqlValue200( result = GetSqlValue200(getters, ordinal, metaData); break; case SqlDbType.Date: + #if NET + result = DateOnly.FromDateTime(GetDateTime_Unchecked(getters, ordinal)); + break; + #endif case SqlDbType.DateTime2: result = GetDateTime_Unchecked(getters, ordinal); break; @@ -1504,7 +1508,7 @@ value is DataFeed } #if NET case ExtendedClrTypeCode.DateOnly: - SetDateTime_Checked(setters, ordinal, metaData, ((DateOnly)value).ToDateTime(new TimeOnly(0, 0))); + SetDate_Checked(setters, ordinal, metaData, ((DateOnly)value).ToDateTime(new TimeOnly(0, 0))); break; case ExtendedClrTypeCode.TimeOnly: SetTimeSpan_Checked(setters, ordinal, metaData, ((TimeOnly)value).ToTimeSpan()); @@ -3135,8 +3139,11 @@ private static void SetDateTime2_Unchecked(ITypedSettersV3 setters, int ordinal, private static void SetDate_Unchecked(ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, DateTime value) { - Debug.Assert(metaData.SqlDbType == SqlDbType.Variant, "Invalid type. This should be called only when the type is variant."); - setters.SetVariantMetaData(ordinal, SmiMetaData.DefaultDate); + if (metaData.SqlDbType == SqlDbType.Variant) + { + setters.SetVariantMetaData(ordinal, SmiMetaData.DefaultDate); + } + setters.SetDateTime(ordinal, value); } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/DateTimeVariantTests.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/DateTimeVariantTests.cs index 3d84e0ff2c..e968295b5a 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/DateTimeVariantTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/DateTimeVariantTests.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. @@ -987,12 +987,7 @@ public static IEnumerable GetParameterCombinations() { #if NET yield return new object[] { DateOnly.MinValue, "date", - new Dictionary { - { TestVariations.TestSimpleParameter_Variant, SqlDateTimeOverflow }, - { TestVariations.TestSqlDataRecordParameterToTVP_Variant, SqlDateTimeOverflow }, - { TestVariations.TestSqlDataReaderParameterToTVP_Variant, SqlDateTimeOverflow }, - { TestVariations.SqlBulkCopyDataTable_Variant, SqlDateTimeOverflow }, - { TestVariations.SqlBulkCopyDataRow_Variant, SqlDateTimeOverflow }}, + new Dictionary(), new Dictionary() { { TestVariations.TestSimpleParameter_Type, new DateTime(0) }, @@ -1014,12 +1009,7 @@ public static IEnumerable GetParameterCombinations() }, new Dictionary()}; yield return new object[] { DateOnly.MaxValue, "date", - new Dictionary { - { TestVariations.TestSimpleParameter_Variant, SqlDateTimeOverflow }, - { TestVariations.TestSqlDataRecordParameterToTVP_Variant, SqlDateTimeOverflow }, - { TestVariations.TestSqlDataReaderParameterToTVP_Variant, SqlDateTimeOverflow }, - { TestVariations.SqlBulkCopyDataTable_Variant, SqlDateTimeOverflow }, - { TestVariations.SqlBulkCopyDataRow_Variant, SqlDateTimeOverflow }}, + new Dictionary(), new Dictionary() { { TestVariations.TestSimpleParameter_Type, new DateTime(3155378112000000000) }, @@ -1040,9 +1030,6 @@ public static IEnumerable GetParameterCombinations() { TestVariations.SqlBulkCopyDataRow_Variant, new DateTime(3155378112000000000) } }, new Dictionary() - { - {TestVariations.TestSqlDataRecordParameterToTVP_Variant, "datetime"} - } }; #endif yield return new object[] { DateTime.MinValue, "date",