diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/ExtendedClrTypeCode.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/ExtendedClrTypeCode.cs index 28818a16f1..953a5d5e55 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/ExtendedClrTypeCode.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/ExtendedClrTypeCode.cs @@ -49,6 +49,10 @@ internal enum ExtendedClrTypeCode IEnumerableOfSqlDataRecord, // System.Collections.Generic.IEnumerable TimeSpan, // System.TimeSpan DateTimeOffset, // System.DateTimeOffset +#if NET6_0_OR_GREATER + DateOnly, // System.DateOnly + TimeOnly, // System.TimeOnly +#endif Stream, // System.IO.Stream TextReader, // System.IO.TextReader XmlReader, // System.Xml.XmlReader diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/MetadataUtilsSmi.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/MetadataUtilsSmi.cs index 590429222a..bd771f7ab1 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/MetadataUtilsSmi.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/MetadataUtilsSmi.cs @@ -75,6 +75,10 @@ internal class MetaDataUtilsSmi SqlDbType.Structured, // System.Collections.Generic.IEnumerable SqlDbType.Time, // System.TimeSpan SqlDbType.DateTimeOffset, // System.DateTimeOffset +#if NET6_0_OR_GREATER + SqlDbType.Date, // System.DateOnly + SqlDbType.Time, // System.TimeOnly +#endif }; @@ -86,7 +90,11 @@ internal class MetaDataUtilsSmi private static Dictionary CreateTypeToExtendedTypeCodeMap() { +#if NET6_0_OR_GREATER + int Count = 44; +#else int Count = 42; +#endif // Keep this initialization list in the same order as ExtendedClrTypeCode for ease in validating! var dictionary = new Dictionary(Count) { @@ -132,6 +140,10 @@ private static Dictionary CreateTypeToExtendedTypeCod { typeof(IEnumerable), ExtendedClrTypeCode.IEnumerableOfSqlDataRecord }, { typeof(TimeSpan), ExtendedClrTypeCode.TimeSpan }, { typeof(DateTimeOffset), ExtendedClrTypeCode.DateTimeOffset }, +#if NET6_0_OR_GREATER + { typeof(DateOnly), ExtendedClrTypeCode.DateOnly }, + { typeof(TimeOnly), ExtendedClrTypeCode.TimeOnly }, +#endif }; return dictionary; } @@ -244,6 +256,16 @@ Type udtType extendedCode = ExtendedClrTypeCode.Char; break; case SqlDbType.Date: +#if NET6_0_OR_GREATER + if (value.GetType() == typeof(DateOnly)) + extendedCode = ExtendedClrTypeCode.DateOnly; + else if (value.GetType() == typeof(DateTime)) + extendedCode = ExtendedClrTypeCode.DateTime; + else if (value.GetType() == typeof(SqlDateTime)) + extendedCode = ExtendedClrTypeCode.SqlDateTime; + + break; +#endif case SqlDbType.DateTime2: #if NETFRAMEWORK if (smiVersion >= SmiContextFactory.Sql2008Version) @@ -330,6 +352,14 @@ Type udtType extendedCode = ExtendedClrTypeCode.Invalid; } break; +#if NET6_0_OR_GREATER + case SqlDbType.Time: + if (value.GetType() == typeof(TimeOnly)) + extendedCode = ExtendedClrTypeCode.TimeOnly; + else if (value.GetType() == typeof(TimeSpan)) + extendedCode = ExtendedClrTypeCode.TimeSpan; + break; +#else case SqlDbType.Time: if (value.GetType() == typeof(TimeSpan) #if NETFRAMEWORK @@ -338,6 +368,7 @@ Type udtType ) extendedCode = ExtendedClrTypeCode.TimeSpan; break; +#endif case SqlDbType.DateTimeOffset: if (value.GetType() == typeof(DateTimeOffset) #if NETFRAMEWORK 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 61bcec417f..4662bc6012 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 @@ -1542,6 +1542,14 @@ value is DataFeed SetCompatibleValue(sink, setters, ordinal, metaData, charsValue, ExtendedClrTypeCode.CharArray, 0); break; } +#if NET6_0_OR_GREATER + case ExtendedClrTypeCode.DateOnly: + SetDateTime_Checked(sink, setters, ordinal, metaData, ((DateOnly)value).ToDateTime(new TimeOnly(0, 0))); + break; + case ExtendedClrTypeCode.TimeOnly: + SetTimeSpan_Checked(sink, (SmiTypedGetterSetter)setters, ordinal, metaData, ((TimeOnly)value).ToTimeSpan()); + break; +#endif case ExtendedClrTypeCode.DateTime: SetDateTime_Checked(sink, setters, ordinal, metaData, (DateTime)value); break; @@ -2899,6 +2907,10 @@ int length /*EnSDR*/{ _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , X, _ , _ , _ , _ , },/*IEnurerable*/ /*TmSpn*/{ _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _, _ , X , _ , _ , },/*TimeSpan*/ /*DTOst*/{ _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _, _ , _ , _ , X , },/*DateTimeOffset*/ +#if NET6_0_OR_GREATER +/*DOnly*/{ _ , _ , _ , _ , X , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , X , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _, X , _ , X , _ , },/*DateOnly*/ +/*TOnly*/{ _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _, _ , X , _ , _ , },/*TimeOnly*/ +#endif /*Strm */{ _ , X , _ , _ , _ , _ , _ , X , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , X , _ , _ , _ , _ , _ , _ , _ , X , _, _ , _ , _ , _ , },/*Stream*/ /*TxRdr*/{ _ , _ , _ , X , _ , _ , _ , _ , _ , _ , X , X , X , _ , _ , _ , _ , _ , X , _ , _ , _ , X , _ , _ , _ , _ , _ , _ , _ , _, _ , _ , _ , _ , },/*TextReader*/ /*XmlRd*/{ _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _, _ , _ , _ , _ , },/*XmlReader*/ @@ -2951,6 +2963,10 @@ int length /*EnSDR*/{ _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , X, _ , _ , _ , _ , },/*IEnurerable*/ /*TmSpn*/{ _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _, _ , X , _ , _ , },/*TimeSpan*/ /*DTOst*/{ _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _, _ , _ , _ , X , },/*DateTimeOffset*/ +#if NET6_0_OR_GREATER +/*DOnly*/{ _ , _ , _ , _ , X , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , X , _ , _ , _ , _ , _ , _ , _ , X , _ , _ , _ , _ , _ , _ , _, X , _ , X , _ , },/*DateOnly*/ +/*TOnly*/{ _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _, _ , X , _ , _ , },/*TimeOnly*/ +#endif /*Strm */{ _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _, _ , _ , _ , _ , },/*Stream*/ /*TxRdr*/{ _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _, _ , _ , _ , _ , },/*TextReader*/ /*XmlRd*/{ _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _, _ , _ , _ , _ , },/*XmlReader*/ diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/ParametersTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/ParametersTest.cs index 3b71cbf851..378a32c07c 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/ParametersTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/ParametersTest.cs @@ -9,6 +9,9 @@ using System.Data.SqlTypes; using System.Threading; using Xunit; +#if NET6_0_OR_GREATER +using Microsoft.Data.SqlClient.Server; +#endif namespace Microsoft.Data.SqlClient.ManualTesting.Tests { @@ -306,6 +309,56 @@ public static void TestParametersWithDatatablesTVPInsert() } } +#if NET6_0_OR_GREATER + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] + public static void TestDateOnlyTVPDataTable_CommandSP() + { + string tableTypeName = "[dbo]." + DataTestUtility.GetUniqueNameForSqlServer("UDTTTestDateOnlyTVP"); + string spName = DataTestUtility.GetUniqueNameForSqlServer("spTestDateOnlyTVP"); + SqlConnection connection = new(s_connString); + try + { + connection.Open(); + using (SqlCommand cmd = connection.CreateCommand()) + { + cmd.CommandType = CommandType.Text; + cmd.CommandText = $"CREATE TYPE {tableTypeName} AS TABLE ([DateColumn] date NULL, [TimeColumn] time NULL)"; + cmd.ExecuteNonQuery(); + cmd.CommandText = $"CREATE PROCEDURE {spName} (@dates {tableTypeName} READONLY) AS SELECT COUNT(*) FROM @dates"; + cmd.ExecuteNonQuery(); + } + using (SqlCommand cmd = connection.CreateCommand()) + { + cmd.CommandText = spName; + cmd.CommandType = CommandType.StoredProcedure; + + DataTable dtTest = new(); + dtTest.Columns.Add(new DataColumn("DateColumn", typeof(DateOnly))); + dtTest.Columns.Add(new DataColumn("TimeColumn", typeof(TimeOnly))); + var dataRow = dtTest.NewRow(); + dataRow["DateColumn"] = new DateOnly(2023, 11, 15); + dataRow["TimeColumn"] = new TimeOnly(12, 30, 45); + dtTest.Rows.Add(dataRow); + + cmd.Parameters.Add(new SqlParameter + { + ParameterName = "@dates", + SqlDbType = SqlDbType.Structured, + TypeName = tableTypeName, + Value = dtTest, + }); + + cmd.ExecuteNonQuery(); + } + } + finally + { + DataTestUtility.DropStoredProcedure(connection, spName); + DataTestUtility.DropUserDefinedType(connection, tableTypeName); + } + } +#endif + #region Scaled Decimal Parameter & TVP Test [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] [InlineData("CAST(1.0 as decimal(38, 37))", "1.0000000000000000000000000000")]