Skip to content

Commit 9db26bd

Browse files
test and fix for Issue #26742 (#26758)
1 parent 87f933f commit 9db26bd

11 files changed

+550
-30
lines changed

src/EFCore.SqlServer/Storage/Internal/SqlServerDateTimeOffsetTypeMapping.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,8 @@ protected override void ConfigureParameter(DbParameter parameter)
107107

108108
if (Precision.HasValue)
109109
{
110-
parameter.Precision = unchecked((byte)Precision.Value);
110+
// Workaround for inconsistant definition of precision/scale between EF and SQLClient for VarTime types
111+
parameter.Scale = unchecked((byte)Precision.Value);
111112
}
112113
}
113114
}

src/EFCore.SqlServer/Storage/Internal/SqlServerDateTimeTypeMapping.cs

+3-2
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public class SqlServerDateTimeTypeMapping : DateTimeTypeMapping
2222
// so the order of the entries in this array is important
2323
private readonly string[] _dateTime2Formats =
2424
{
25-
"'{0:yyyy-MM-ddTHH:mm:ss}'",
25+
"'{0:yyyy-MM-ddTHH:mm:ssK}'",
2626
"'{0:yyyy-MM-ddTHH:mm:ss.fK}'",
2727
"'{0:yyyy-MM-ddTHH:mm:ss.ffK}'",
2828
"'{0:yyyy-MM-ddTHH:mm:ss.fffK}'",
@@ -86,7 +86,8 @@ protected override void ConfigureParameter(DbParameter parameter)
8686

8787
if (Precision.HasValue)
8888
{
89-
parameter.Precision = unchecked((byte)Precision.Value);
89+
// Workaround for inconsistant definition of precision/scale between EF and SQLClient for VarTime types
90+
parameter.Scale = unchecked((byte)Precision.Value);
9091
}
9192
}
9293

src/EFCore.SqlServer/Storage/Internal/SqlServerTimeSpanTypeMapping.cs

+67-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System.Data;
5+
using System.Globalization;
56
using Microsoft.Data.SqlClient;
67

78
namespace Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal;
@@ -14,14 +15,36 @@ namespace Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal;
1415
/// </summary>
1516
public class SqlServerTimeSpanTypeMapping : TimeSpanTypeMapping
1617
{
18+
// Note: this array will be accessed using the precision as an index
19+
// so the order of the entries in this array is important
20+
private readonly string[] _timeFormats =
21+
{
22+
@"'{0:hh\:mm\:ss}'",
23+
@"'{0:hh\:mm\:ss\.F}'",
24+
@"'{0:hh\:mm\:ss\.FF}'",
25+
@"'{0:hh\:mm\:ss\.FFF}'",
26+
@"'{0:hh\:mm\:ss\.FFFF}'",
27+
@"'{0:hh\:mm\:ss\.FFFFF}'",
28+
@"'{0:hh\:mm\:ss\.FFFFFF}'",
29+
@"'{0:hh\:mm\:ss\.FFFFFFF}'"
30+
};
31+
1732
/// <summary>
1833
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
1934
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
2035
/// any release. You should only use it directly in your code with extreme caution and knowing that
2136
/// doing so can result in application failures when updating to a new Entity Framework Core release.
2237
/// </summary>
23-
public SqlServerTimeSpanTypeMapping(string storeType, DbType? dbType = System.Data.DbType.Time)
24-
: base(storeType, dbType)
38+
public SqlServerTimeSpanTypeMapping(
39+
string storeType,
40+
DbType? dbType = System.Data.DbType.Time,
41+
StoreTypePostfix storeTypePostfix = StoreTypePostfix.Precision)
42+
: base(
43+
new RelationalTypeMappingParameters(
44+
new CoreTypeMappingParameters(typeof(TimeSpan)),
45+
storeType,
46+
storeTypePostfix,
47+
dbType))
2548
{
2649
}
2750

@@ -59,5 +82,47 @@ protected override void ConfigureParameter(DbParameter parameter)
5982
{
6083
((SqlParameter)parameter).SqlDbType = SqlDbType.Time;
6184
}
85+
if (Precision.HasValue)
86+
{
87+
parameter.Scale = unchecked((byte)Precision.Value);
88+
}
89+
}
90+
91+
/// <summary>
92+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
93+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
94+
/// any release. You should only use it directly in your code with extreme caution and knowing that
95+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
96+
/// </summary>
97+
protected override string SqlLiteralFormatString
98+
{
99+
get
100+
{
101+
if (Precision.HasValue)
102+
{
103+
var precision = Precision.Value;
104+
if (precision <= 7
105+
&& precision >= 0)
106+
{
107+
return _timeFormats[precision];
108+
}
109+
}
110+
111+
return _timeFormats[7];
112+
}
113+
}
114+
115+
/// <summary>
116+
/// Generates the SQL representation of a literal value without conversion.
117+
/// </summary>
118+
/// <param name="value">The literal value.</param>
119+
/// <returns>
120+
/// The generated string.
121+
/// </returns>
122+
protected override string GenerateNonNullSqlLiteral(object value)
123+
{
124+
return value is TimeSpan timeSpan && timeSpan.Milliseconds == 0
125+
? string.Format(CultureInfo.InvariantCulture, _timeFormats[0], value) //handle trailing decimal seperator when no fractional seconds
126+
: string.Format(CultureInfo.InvariantCulture, SqlLiteralFormatString, value);
62127
}
63128
}

src/EFCore.SqlServer/Storage/Internal/SqlServerTypeMappingSource.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,8 @@ public SqlServerTypeMappingSource(
347347
"datetime2",
348348
"datetimeoffset",
349349
"double precision",
350-
"float"
350+
"float",
351+
"time"
351352
};
352353

353354
/// <summary>

test/EFCore.Relational.Tests/Storage/RelationalTypeMappingTest.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -495,7 +495,7 @@ public virtual void String_literal_generated_correctly()
495495

496496
[ConditionalFact]
497497
public virtual void Timespan_literal_generated_correctly()
498-
=> Test_GenerateSqlLiteral_helper(new TimeSpanTypeMapping("time"), new TimeSpan(7, 14, 30), "'07:14:30'");
498+
=> Test_GenerateSqlLiteral_helper(new TimeSpanTypeMapping("time"), new TimeSpan(0, 7, 14, 30, 123), "'07:14:30.1230000'");
499499

500500
[ConditionalFact]
501501
public virtual void UInt_literal_generated_correctly()

test/EFCore.SqlServer.FunctionalTests/BuiltInDataTypesSqlServerTest.cs

+34-13
Original file line numberDiff line numberDiff line change
@@ -1574,15 +1574,16 @@ public virtual void Can_insert_and_read_back_all_mapped_data_types_with_scale()
15741574
var parameters = DumpParameters();
15751575
Assert.Equal(
15761576
@"@p0='77'
1577-
@p1='2017-01-02T12:11:12.3210000' (Precision = 3)
1578-
@p2='2016-01-02T11:11:12.7650000+00:00' (Precision = 3)
1577+
@p1='2017-01-02T12:11:12.3210000' (Scale = 3)
1578+
@p2='2016-01-02T11:11:12.7650000+00:00' (Scale = 3)
15791579
@p3='102' (Precision = 3)
15801580
@p4='101' (Precision = 3)
15811581
@p5='103' (Precision = 3)
15821582
@p6='85.55000305175781' (Size = 25)
15831583
@p7='85.5' (Size = 3)
15841584
@p8='83.33000183105469' (Size = 25)
1585-
@p9='83.3' (Size = 3)",
1585+
@p9='83.3' (Size = 3)
1586+
@p10='12:34:56.7890123' (Scale = 3)",
15861587
parameters,
15871588
ignoreLineEndingDifferences: true);
15881589

@@ -1602,6 +1603,7 @@ private static void AssertMappedScaledDataTypes(MappedScaledDataTypes entity, in
16021603
Assert.Equal(
16031604
new DateTimeOffset(new DateTime(2016, 1, 2, 11, 11, 12, 765), TimeSpan.Zero), entity.DateTimeOffsetAsDatetimeoffset3);
16041605
Assert.Equal(new DateTime(2017, 1, 2, 12, 11, 12, 321), entity.DateTimeAsDatetime23);
1606+
Assert.Equal(TimeSpan.Parse("12:34:56.789", System.Globalization.CultureInfo.InvariantCulture), entity.TimeSpanAsTime3);
16051607
Assert.Equal(101m, entity.DecimalAsDecimal3);
16061608
Assert.Equal(102m, entity.DecimalAsDec3);
16071609
Assert.Equal(103m, entity.DecimalAsNumeric3);
@@ -1619,7 +1621,8 @@ private static MappedScaledDataTypes CreateMappedScaledDataTypes(int id)
16191621
DateTimeAsDatetime23 = new DateTime(2017, 1, 2, 12, 11, 12, 321),
16201622
DecimalAsDecimal3 = 101m,
16211623
DecimalAsDec3 = 102m,
1622-
DecimalAsNumeric3 = 103m
1624+
DecimalAsNumeric3 = 103m,
1625+
TimeSpanAsTime3 = TimeSpan.Parse("12:34:56.7890123", System.Globalization.CultureInfo.InvariantCulture)
16231626
};
16241627

16251628
[ConditionalFact]
@@ -1635,15 +1638,16 @@ public virtual void Can_insert_and_read_back_all_mapped_data_types_with_scale_se
16351638
var parameters = DumpParameters();
16361639
Assert.Equal(
16371640
@"@p0='77'
1638-
@p1='2017-01-02T12:11:12.3210000' (Precision = 3)
1639-
@p2='2016-01-02T11:11:12.7650000+00:00' (Precision = 3)
1641+
@p1='2017-01-02T12:11:12.3210000' (Scale = 3)
1642+
@p2='2016-01-02T11:11:12.7650000+00:00' (Scale = 3)
16401643
@p3='102' (Precision = 3)
16411644
@p4='101' (Precision = 3)
16421645
@p5='103' (Precision = 3)
16431646
@p6='85.55000305175781' (Size = 25)
16441647
@p7='85.5' (Size = 3)
16451648
@p8='83.33000183105469' (Size = 25)
1646-
@p9='83.3' (Size = 3)",
1649+
@p9='83.3' (Size = 3)
1650+
@p10='12:34:56.7890000' (Scale = 3)",
16471651
parameters,
16481652
ignoreLineEndingDifferences: true);
16491653

@@ -1666,6 +1670,7 @@ private static void AssertMappedScaledSeparatelyDataTypes(MappedScaledSeparately
16661670
Assert.Equal(101m, entity.DecimalAsDecimal3);
16671671
Assert.Equal(102m, entity.DecimalAsDec3);
16681672
Assert.Equal(103m, entity.DecimalAsNumeric3);
1673+
Assert.Equal(TimeSpan.Parse("12:34:56.789", System.Globalization.CultureInfo.InvariantCulture), entity.TimeSpanAsTime3);
16691674
}
16701675

16711676
private static MappedScaledSeparatelyDataTypes CreateMappedScaledSeparatelyDataTypes(int id)
@@ -1680,7 +1685,8 @@ private static MappedScaledSeparatelyDataTypes CreateMappedScaledSeparatelyDataT
16801685
DateTimeAsDatetime23 = new DateTime(2017, 1, 2, 12, 11, 12, 321),
16811686
DecimalAsDecimal3 = 101m,
16821687
DecimalAsDec3 = 102m,
1683-
DecimalAsNumeric3 = 103m
1688+
DecimalAsNumeric3 = 103m,
1689+
TimeSpanAsTime3 = TimeSpan.Parse("12:34:56.789", System.Globalization.CultureInfo.InvariantCulture)
16841690
};
16851691

16861692
[ConditionalFact]
@@ -2480,18 +2486,19 @@ public virtual void Can_insert_and_read_back_all_mapped_data_types_with_scale_wi
24802486
Assert.Equal(1, context.SaveChanges());
24812487
}
24822488

2483-
var parameters = DumpParameters();
2489+
var parameters = DumpParameters();
24842490
Assert.Equal(
2485-
@"@p0='2017-01-02T12:11:12.1230000' (Precision = 3)
2486-
@p1='2016-01-02T11:11:12.5670000+00:00' (Precision = 3)
2491+
@"@p0='2017-01-02T12:11:12.1230000' (Scale = 3)
2492+
@p1='2016-01-02T11:11:12.5670000+00:00' (Scale = 3)
24872493
@p2='102' (Precision = 3)
24882494
@p3='101' (Precision = 3)
24892495
@p4='103' (Precision = 3)
24902496
@p5='85.55000305175781' (Size = 25)
24912497
@p6='85.5' (Size = 3)
24922498
@p7='83.33000183105469' (Size = 25)
24932499
@p8='83.3' (Size = 3)
2494-
@p9='77'",
2500+
@p9='77'
2501+
@p10='12:34:56.7890123' (Scale = 3)",
24952502
parameters,
24962503
ignoreLineEndingDifferences: true);
24972504

@@ -2514,6 +2521,7 @@ private static void AssertMappedScaledDataTypesWithIdentity(MappedScaledDataType
25142521
Assert.Equal(101m, entity.DecimalAsDecimal3);
25152522
Assert.Equal(102m, entity.DecimalAsDec3);
25162523
Assert.Equal(103m, entity.DecimalAsNumeric3);
2524+
Assert.Equal(TimeSpan.Parse("12:34:56.789", System.Globalization.CultureInfo.InvariantCulture), entity.TimeSpanAsTime3);
25172525
}
25182526

25192527
private static MappedScaledDataTypesWithIdentity CreateMappedScaledDataTypesWithIdentity(int id)
@@ -2528,7 +2536,8 @@ private static MappedScaledDataTypesWithIdentity CreateMappedScaledDataTypesWith
25282536
DateTimeAsDatetime23 = new DateTime(2017, 1, 2, 12, 11, 12, 123),
25292537
DecimalAsDecimal3 = 101m,
25302538
DecimalAsDec3 = 102m,
2531-
DecimalAsNumeric3 = 103m
2539+
DecimalAsNumeric3 = 103m,
2540+
TimeSpanAsTime3 = TimeSpan.Parse("12:34:56.7890123", System.Globalization.CultureInfo.InvariantCulture)
25322541
};
25332542

25342543
[ConditionalFact]
@@ -3232,6 +3241,7 @@ public virtual void Columns_have_expected_data_types()
32323241
MappedScaledDataTypes.FloatAsFloat25 ---> [float] [Precision = 53]
32333242
MappedScaledDataTypes.FloatAsFloat3 ---> [real] [Precision = 24]
32343243
MappedScaledDataTypes.Id ---> [int] [Precision = 10 Scale = 0]
3244+
MappedScaledDataTypes.TimeSpanAsTime3 ---> [time] [Precision = 3]
32353245
MappedScaledDataTypesWithIdentity.DateTimeAsDatetime23 ---> [datetime2] [Precision = 3]
32363246
MappedScaledDataTypesWithIdentity.DateTimeOffsetAsDatetimeoffset3 ---> [datetimeoffset] [Precision = 3]
32373247
MappedScaledDataTypesWithIdentity.DecimalAsDec3 ---> [decimal] [Precision = 3 Scale = 0]
@@ -3243,6 +3253,7 @@ public virtual void Columns_have_expected_data_types()
32433253
MappedScaledDataTypesWithIdentity.FloatAsFloat3 ---> [real] [Precision = 24]
32443254
MappedScaledDataTypesWithIdentity.Id ---> [int] [Precision = 10 Scale = 0]
32453255
MappedScaledDataTypesWithIdentity.Int ---> [int] [Precision = 10 Scale = 0]
3256+
MappedScaledDataTypesWithIdentity.TimeSpanAsTime3 ---> [time] [Precision = 3]
32463257
MappedScaledSeparatelyDataTypes.DateTimeAsDatetime23 ---> [datetime2] [Precision = 3]
32473258
MappedScaledSeparatelyDataTypes.DateTimeOffsetAsDatetimeoffset3 ---> [datetimeoffset] [Precision = 3]
32483259
MappedScaledSeparatelyDataTypes.DecimalAsDec3 ---> [decimal] [Precision = 3 Scale = 0]
@@ -3253,6 +3264,7 @@ public virtual void Columns_have_expected_data_types()
32533264
MappedScaledSeparatelyDataTypes.FloatAsFloat25 ---> [float] [Precision = 53]
32543265
MappedScaledSeparatelyDataTypes.FloatAsFloat3 ---> [real] [Precision = 24]
32553266
MappedScaledSeparatelyDataTypes.Id ---> [int] [Precision = 10 Scale = 0]
3267+
MappedScaledSeparatelyDataTypes.TimeSpanAsTime3 ---> [time] [Precision = 3]
32563268
MappedSizedDataTypes.BytesAsBinary3 ---> [nullable binary] [MaxLength = 3]
32573269
MappedSizedDataTypes.BytesAsBinaryVarying3 ---> [nullable varbinary] [MaxLength = 3]
32583270
MappedSizedDataTypes.BytesAsVarbinary3 ---> [nullable varbinary] [MaxLength = 3]
@@ -4127,6 +4139,9 @@ protected class MappedScaledDataTypes
41274139

41284140
[Column(TypeName = "numeric(3)")]
41294141
public decimal DecimalAsNumeric3 { get; set; }
4142+
4143+
[Column(TypeName = "time(3)")]
4144+
public TimeSpan TimeSpanAsTime3 { get; set; }
41304145
}
41314146

41324147
protected class MappedScaledSeparatelyDataTypes
@@ -4159,6 +4174,9 @@ protected class MappedScaledSeparatelyDataTypes
41594174

41604175
[Column(TypeName = "numeric")]
41614176
public decimal DecimalAsNumeric3 { get; set; }
4177+
4178+
[Column(TypeName = "time(3)")]
4179+
public TimeSpan TimeSpanAsTime3 { get; set; }
41624180
}
41634181

41644182
protected class DoubleDataTypes
@@ -4611,6 +4629,9 @@ protected class MappedScaledDataTypesWithIdentity
46114629

46124630
[Column(TypeName = "numeric(3)")]
46134631
public decimal DecimalAsNumeric3 { get; set; }
4632+
4633+
[Column(TypeName = "time(3)")]
4634+
public TimeSpan TimeSpanAsTime3 { get; set; }
46144635
}
46154636

46164637
protected class MappedPrecisionAndScaledDataTypesWithIdentity

0 commit comments

Comments
 (0)