Skip to content

Commit 1c03b1d

Browse files
authored
Add support for DateOnly and TimeOnly (SqlParameter value and GetFieldValue(Async) ) (#1813)
1 parent 708cf3a commit 1c03b1d

File tree

8 files changed

+566
-21
lines changed

8 files changed

+566
-21
lines changed

doc/snippets/Microsoft.Data.SqlClient/SqlDataReader.xml

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -327,14 +327,14 @@
327327
328328
|||||
329329
|-|-|-|-|
330-
|Boolean|Byte|Char|DateTime|
331-
|DateTimeOffset|Decimal|Double|Float|
332-
|Guid|Int16|Int32|Int64|
333-
|SqlBoolean|SqlByte|SqlDateTime|SqlDecimal|
334-
|SqlDouble|SqlGuid|SqlInt16|SqlInt32|
335-
|SqlInt64|SqlMoney|SqlSingle|SqlString|
336-
|Stream|String|TextReader|UDT, which can be any CLR type marked with <xref:Microsoft.Data.SqlClient.Server.SqlUserDefinedTypeAttribute>.|
337-
|XmlReader||||
330+
|Boolean|Byte|Char|DateOnly (.NET 6 or later)|
331+
|DateTime|DateTimeOffset|Decimal|Double|
332+
|Float|Guid|Int16|Int32|
333+
|Int64|SqlBoolean|SqlByte|SqlDateTime|
334+
|SqlDecimal|SqlDouble|SqlGuid|SqlInt16|
335+
|SqlInt32|SqlInt64|SqlMoney|SqlSingle|
336+
|SqlString|Stream|String|TextReader|
337+
|TimeOnly (.NET 6 or later)|XmlReader||UDT, which can be any CLR type marked with <xref:Microsoft.Data.SqlClient.Server.SqlUserDefinedTypeAttribute>.|
338338
339339
For more information, see [SqlClient Streaming Support](/sql/connect/ado-net/sqlclient-streaming-support).
340340
@@ -369,14 +369,14 @@
369369
370370
|||||
371371
|-|-|-|-|
372-
|Boolean|Byte|Char|DateTime|
373-
|DateTimeOffset|Decimal|Double|Float|
374-
|Guid|Int16|Int32|Int64|
375-
|SqlBoolean|SqlByte|SqlDateTime|SqlDecimal|
376-
|SqlDouble|SqlGuid|SqlInt16|SqlInt32|
377-
|SqlInt64|SqlMoney|SqlSingle|SqlString|
378-
|Stream|String|TextReader|UDT, which can be any CLR type marked with <xref:Microsoft.Data.SqlClient.Server.SqlUserDefinedTypeAttribute>.|
379-
|XmlReader||||
372+
|Boolean|Byte|Char|DateOnly (.NET 6 or later)|
373+
|DateTime|DateTimeOffset|Decimal|Double|
374+
|Float|Guid|Int16|Int32|
375+
|Int64|SqlBoolean|SqlByte|SqlDateTime|
376+
|SqlDecimal|SqlDouble|SqlGuid|SqlInt16|
377+
|SqlInt32|SqlInt64|SqlMoney|SqlSingle|
378+
|SqlString|Stream|String|TextReader|
379+
|TimeOnly (.NET 6 or later)|XmlReader||UDT, which can be any CLR type marked with <xref:Microsoft.Data.SqlClient.Server.SqlUserDefinedTypeAttribute>.|
380380
381381
For more information, see [SqlClient Streaming Support](/sql/connect/ado-net/sqlclient-streaming-support).
382382

src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlDataReader.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2843,6 +2843,16 @@ private T GetFieldValueFromSqlBufferInternal<T>(SqlBuffer data, _SqlMetaData met
28432843
{
28442844
return (T)(object)data.DateTime;
28452845
}
2846+
#if NET6_0_OR_GREATER
2847+
else if (typeof(T) == typeof(DateOnly) && dataType == typeof(DateTime) && _typeSystem > SqlConnectionString.TypeSystem.SQLServer2005 && metaData.Is2008DateTimeType)
2848+
{
2849+
return (T)(object)data.DateOnly;
2850+
}
2851+
else if (typeof(T) == typeof(TimeOnly) && dataType == typeof(TimeOnly) && _typeSystem > SqlConnectionString.TypeSystem.SQLServer2005 && metaData.Is2008DateTimeType)
2852+
{
2853+
return (T)(object)data.TimeOnly;
2854+
}
2855+
#endif
28462856
else if (typeof(T) == typeof(XmlReader))
28472857
{
28482858
// XmlReader only allowed on XML types

src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBuffer.cs

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -596,6 +596,37 @@ internal TimeSpan Time
596596
}
597597
}
598598

599+
#if NET6_0_OR_GREATER
600+
internal TimeOnly TimeOnly
601+
{
602+
get
603+
{
604+
ThrowIfNull();
605+
606+
if (StorageType.Time == _type)
607+
{
608+
return new TimeOnly(_value._timeInfo._ticks);
609+
}
610+
611+
return (TimeOnly)Value; // anything else we haven't thought of goes through boxing.
612+
}
613+
}
614+
615+
internal DateOnly DateOnly
616+
{
617+
get
618+
{
619+
ThrowIfNull();
620+
621+
if (StorageType.Date == _type)
622+
{
623+
return DateOnly.MinValue.AddDays(_value._int32);
624+
}
625+
return (DateOnly)Value; // anything else we haven't thought of goes through boxing.
626+
}
627+
}
628+
#endif
629+
599630
internal DateTimeOffset DateTimeOffset
600631
{
601632
get
@@ -1097,7 +1128,7 @@ internal Type GetTypeFromStorageType(bool isSqlType)
10971128
return typeof(SqlGuid);
10981129
case StorageType.SqlXml:
10991130
return typeof(SqlXml);
1100-
// Date DateTime2 and DateTimeOffset have no direct Sql type to contain them
1131+
// Time Date DateTime2 and DateTimeOffset have no direct Sql type to contain them
11011132
}
11021133
}
11031134
else
@@ -1144,6 +1175,10 @@ internal Type GetTypeFromStorageType(bool isSqlType)
11441175
return typeof(DateTime);
11451176
case StorageType.DateTimeOffset:
11461177
return typeof(DateTimeOffset);
1178+
#if NET6_0_OR_GREATER
1179+
case StorageType.Time:
1180+
return typeof(TimeOnly);
1181+
#endif
11471182
}
11481183
}
11491184

src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlEnums.cs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,16 @@ private static MetaType GetMetaTypeFromValue(Type dataType, object value, bool i
365365
{
366366
return MetaDateTimeOffset;
367367
}
368+
#if NET6_0_OR_GREATER
369+
else if (dataType == typeof(DateOnly))
370+
{
371+
return s_metaDate;
372+
}
373+
else if (dataType == typeof(TimeOnly))
374+
{
375+
return MetaTime;
376+
}
377+
#endif
368378
else
369379
{
370380
// UDT ?
@@ -630,6 +640,10 @@ internal static object GetSqlValueFromComVariant(object comVal)
630640
break;
631641
case TimeSpan:
632642
case DateTimeOffset:
643+
#if NET6_0_OR_GREATER
644+
case TimeOnly:
645+
case DateOnly:
646+
#endif
633647
sqlVal = comVal;
634648
break;
635649
default:
@@ -739,7 +753,7 @@ internal static SqlDbType GetSqlDbTypeFromOleDbType(short dbType, string typeNam
739753
break; // no direct mapping, just use SqlDbType.Variant;
740754
}
741755
return sqlType;
742-
#else
756+
#else
743757
// OleDbTypes not supported
744758
return SqlDbType.Variant;
745759
#endif // NETFRAMEWORK

src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlParameter.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2252,6 +2252,16 @@ internal static object CoerceValue(object value, MetaType destinationType, out b
22522252
{
22532253
value = new DateTimeOffset((DateTime)value);
22542254
}
2255+
#if NET6_0_OR_GREATER
2256+
else if ((currentType == typeof(DateOnly)) && (destinationType.SqlDbType == SqlDbType.Date))
2257+
{
2258+
value = ((DateOnly)value).ToDateTime(new TimeOnly(0, 0));
2259+
}
2260+
else if ((currentType == typeof(TimeOnly)) && (destinationType.SqlDbType == SqlDbType.Time))
2261+
{
2262+
value = ((TimeOnly)value).ToTimeSpan();
2263+
}
2264+
#endif
22552265
else if (
22562266
TdsEnums.SQLTABLE == destinationType.TDSType &&
22572267
(

src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlParameterTest.cs

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,64 @@ public void Constructor2_Value_DateTime()
9797
Assert.Equal(string.Empty, p.XmlSchemaCollectionOwningSchema);
9898
}
9999

100+
#if NET6_0_OR_GREATER
101+
[Fact]
102+
public void Constructor2_Value_DateOnly()
103+
{
104+
DateOnly value = new DateOnly(2004, 8, 24);
105+
SqlParameter p = new SqlParameter("dateonly", value);
106+
107+
Assert.Equal(DbType.Date, p.DbType);
108+
Assert.Equal(ParameterDirection.Input, p.Direction);
109+
Assert.False(p.IsNullable);
110+
Assert.Equal(0, p.LocaleId);
111+
Assert.Equal(0, p.Offset);
112+
Assert.Equal("dateonly", p.ParameterName);
113+
Assert.Equal(0, p.Precision);
114+
Assert.Equal(0, p.Scale);
115+
Assert.Equal(0, p.Size);
116+
Assert.Equal(string.Empty, p.SourceColumn);
117+
Assert.False(p.SourceColumnNullMapping);
118+
Assert.Equal(DataRowVersion.Current, p.SourceVersion);
119+
Assert.Equal(SqlDbType.Date, p.SqlDbType);
120+
Assert.Equal(value, p.SqlValue);
121+
Assert.Equal(string.Empty, p.TypeName);
122+
Assert.Equal(string.Empty, p.UdtTypeName);
123+
Assert.Equal(value, p.Value);
124+
Assert.Equal(string.Empty, p.XmlSchemaCollectionDatabase);
125+
Assert.Equal(string.Empty, p.XmlSchemaCollectionName);
126+
Assert.Equal(string.Empty, p.XmlSchemaCollectionOwningSchema);
127+
}
128+
129+
[Fact]
130+
public void Constructor2_Value_TimeOnly()
131+
{
132+
TimeOnly value = new TimeOnly(9, 7, 42, 321);
133+
SqlParameter p = new SqlParameter("timeonly", value);
134+
135+
Assert.Equal(DbType.Time, p.DbType);
136+
Assert.Equal(ParameterDirection.Input, p.Direction);
137+
Assert.False(p.IsNullable);
138+
Assert.Equal(0, p.LocaleId);
139+
Assert.Equal(0, p.Offset);
140+
Assert.Equal("timeonly", p.ParameterName);
141+
Assert.Equal(0, p.Precision);
142+
Assert.Equal(0, p.Scale);
143+
Assert.Equal(0, p.Size);
144+
Assert.Equal(string.Empty, p.SourceColumn);
145+
Assert.False(p.SourceColumnNullMapping);
146+
Assert.Equal(DataRowVersion.Current, p.SourceVersion);
147+
Assert.Equal(SqlDbType.Time, p.SqlDbType);
148+
Assert.Equal(value, p.SqlValue);
149+
Assert.Equal(string.Empty, p.TypeName);
150+
Assert.Equal(string.Empty, p.UdtTypeName);
151+
Assert.Equal(value, p.Value);
152+
Assert.Equal(string.Empty, p.XmlSchemaCollectionDatabase);
153+
Assert.Equal(string.Empty, p.XmlSchemaCollectionName);
154+
Assert.Equal(string.Empty, p.XmlSchemaCollectionOwningSchema);
155+
}
156+
#endif
157+
100158
[Fact]
101159
public void Constructor2_Value_Null()
102160
{
@@ -383,6 +441,58 @@ public void InferType_CharArray()
383441
Assert.Equal(value, p.Value);
384442
}
385443

444+
#if NET6_0_OR_GREATER
445+
[Fact]
446+
public void InferType_DateOnly()
447+
{
448+
DateOnly value;
449+
SqlParameter param;
450+
451+
value = DateOnly.FromDateTime(DateTime.Now.Date);
452+
param = new SqlParameter();
453+
param.Value = value;
454+
Assert.Equal(SqlDbType.Date, param.SqlDbType);
455+
Assert.Equal(DbType.Date, param.DbType);
456+
457+
value = DateOnly.FromDateTime(DateTime.Now.Date);
458+
param = new SqlParameter();
459+
param.Value = value;
460+
Assert.Equal(SqlDbType.Date, param.SqlDbType);
461+
Assert.Equal(DbType.Date, param.DbType);
462+
463+
value = DateOnly.FromDateTime(new DateTime(1973, 8, 13));
464+
param = new SqlParameter();
465+
param.Value = value;
466+
Assert.Equal(SqlDbType.Date, param.SqlDbType);
467+
Assert.Equal(DbType.Date, param.DbType);
468+
}
469+
470+
[Fact]
471+
public void InferType_TimeOnly()
472+
{
473+
TimeOnly value;
474+
SqlParameter param;
475+
476+
value = TimeOnly.FromDateTime(DateTime.Now);
477+
param = new SqlParameter();
478+
param.Value = value;
479+
Assert.Equal(SqlDbType.Time, param.SqlDbType);
480+
Assert.Equal(DbType.Time, param.DbType);
481+
482+
value = TimeOnly.FromDateTime(DateTime.Now);
483+
param = new SqlParameter();
484+
param.Value = value;
485+
Assert.Equal(SqlDbType.Time, param.SqlDbType);
486+
Assert.Equal(DbType.Time, param.DbType);
487+
488+
value = TimeOnly.FromDateTime(new DateTime(2022, 10, 22, 15, 27, 38));
489+
param = new SqlParameter();
490+
param.Value = value;
491+
Assert.Equal(SqlDbType.Time, param.SqlDbType);
492+
Assert.Equal(DbType.Time, param.DbType);
493+
}
494+
#endif
495+
386496
[Fact]
387497
public void InferType_DateTime()
388498
{

0 commit comments

Comments
 (0)