Skip to content

Commit d703312

Browse files
committed
Properly read a TimeSpan value from the database.
Add member translator for timespan so SQL works
1 parent 096a095 commit d703312

3 files changed

Lines changed: 116 additions & 31 deletions

File tree

src/EFCore.Jet.Data/JetDataReader.cs

Lines changed: 48 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public JetDataReader(DbDataReader dataReader)
2020
#endif
2121
_wrappedDataReader = dataReader;
2222
}
23-
23+
2424
public JetDataReader(DbDataReader dataReader, int skipCount)
2525
: this(dataReader)
2626
{
@@ -50,8 +50,8 @@ public override int FieldCount
5050
public override bool GetBoolean(int ordinal)
5151
{
5252
var value = _wrappedDataReader.GetValue(ordinal);
53-
54-
if (JetConfiguration.UseDefaultValueOnDBNullConversionError &&
53+
54+
if (JetConfiguration.UseDefaultValueOnDBNullConversionError &&
5555
Convert.IsDBNull(value))
5656
{
5757
return default;
@@ -77,40 +77,40 @@ public override bool GetBoolean(int ordinal)
7777
return ulongValue != 0;
7878
if (value is decimal decimalValue)
7979
return decimalValue != 0;
80-
81-
return (bool) value;
80+
81+
return (bool)value;
8282
}
8383

8484
public override byte GetByte(int ordinal)
8585
{
8686
var value = _wrappedDataReader.GetValue(ordinal);
87-
88-
if (JetConfiguration.UseDefaultValueOnDBNullConversionError &&
87+
88+
if (JetConfiguration.UseDefaultValueOnDBNullConversionError &&
8989
Convert.IsDBNull(value))
9090
{
9191
return default;
9292
}
93-
93+
9494
if (value is byte byteValue)
9595
return byteValue;
9696
if (value is sbyte sbyteValue)
97-
return checked((byte) sbyteValue);
97+
return checked((byte)sbyteValue);
9898
if (value is short shortValue)
99-
return checked((byte) shortValue);
99+
return checked((byte)shortValue);
100100
if (value is ushort ushortValue)
101-
return checked((byte) ushortValue);
101+
return checked((byte)ushortValue);
102102
if (value is int intValue)
103-
return checked((byte) intValue);
103+
return checked((byte)intValue);
104104
if (value is uint uintValue)
105-
return checked((byte) uintValue);
105+
return checked((byte)uintValue);
106106
if (value is long longValue)
107-
return checked((byte) longValue);
107+
return checked((byte)longValue);
108108
if (value is ulong ulongValue)
109-
return checked((byte) ulongValue);
109+
return checked((byte)ulongValue);
110110
if (value is decimal decimalValue)
111-
return (byte) decimalValue;
112-
113-
return (byte) value;
111+
return (byte)decimalValue;
112+
113+
return (byte)value;
114114
}
115115

116116
public override long GetBytes(int ordinal, long dataOffset, byte[]? buffer, int bufferOffset, int length)
@@ -125,7 +125,7 @@ public override char GetChar(int ordinal)
125125
return JetConfiguration.UseDefaultValueOnDBNullConversionError &&
126126
Convert.IsDBNull(value)
127127
? default
128-
: (char) value;
128+
: (char)value;
129129
}
130130

131131
public override long GetChars(int ordinal, long dataOffset, char[]? buffer, int bufferOffset, int length)
@@ -135,7 +135,7 @@ public override long GetChars(int ordinal, long dataOffset, char[]? buffer, int
135135
{
136136
return 0;
137137
}
138-
138+
139139
return _wrappedDataReader.GetChars(ordinal, dataOffset, buffer, bufferOffset, length);
140140
}
141141

@@ -148,7 +148,7 @@ public override DateTime GetDateTime(int ordinal)
148148
// Since DATETIME values are really just DOUBLE values internally in Jet, we explicitly convert those vales
149149
// to DOUBLE in the most outer SELECT projections as a workaround.
150150
var value = _wrappedDataReader.GetValue(ordinal);
151-
151+
152152
if (JetConfiguration.UseDefaultValueOnDBNullConversionError &&
153153
Convert.IsDBNull(value))
154154
return default;
@@ -158,15 +158,15 @@ public override DateTime GetDateTime(int ordinal)
158158
// Round to milliseconds.
159159
return new DateTime(
160160
JetConfiguration.TimeSpanOffset.Ticks +
161-
(long) (Math.Round(
162-
(decimal) (long) ((decimal) doubleValue * TimeSpan.TicksPerDay) /
161+
(long)(Math.Round(
162+
(decimal)(long)((decimal)doubleValue * TimeSpan.TicksPerDay) /
163163
TimeSpan.TicksPerMillisecond,
164164
0,
165165
MidpointRounding.AwayFromZero) *
166166
TimeSpan.TicksPerMillisecond));
167167
}
168168

169-
return (DateTime) value;
169+
return (DateTime)value;
170170
}
171171

172172
public virtual TimeSpan GetTimeSpan(int ordinal)
@@ -261,7 +261,7 @@ public override string GetName(int ordinal)
261261

262262
public override int GetOrdinal(string name)
263263
=> _wrappedDataReader.GetOrdinal(name);
264-
264+
265265
public override DataTable GetSchemaTable()
266266
=> _wrappedDataReader.GetSchemaTable();
267267

@@ -271,13 +271,13 @@ public override string GetString(int ordinal)
271271
return JetConfiguration.UseDefaultValueOnDBNullConversionError &&
272272
Convert.IsDBNull(value)
273273
? string.Empty
274-
: (string) value;
274+
: (string)value;
275275
}
276276

277277
public override object GetValue(int ordinal)
278278
{
279279
var fieldType = GetFieldType(ordinal);
280-
280+
281281
if (fieldType == typeof(bool))
282282
return GetBoolean(ordinal);
283283
if (fieldType == typeof(byte))
@@ -325,10 +325,10 @@ public override object GetValue(int ordinal)
325325
public override int GetValues(object[] values)
326326
{
327327
var count = Math.Min((values ?? throw new ArgumentNullException(nameof(values))).Length, FieldCount);
328-
328+
329329
for (var i = 0; i < count; i++)
330330
values[i] = GetValue(i);
331-
331+
332332
return count;
333333
}
334334

@@ -340,7 +340,7 @@ public override bool IsClosed
340340

341341
public override bool IsDBNull(int ordinal)
342342
=> _wrappedDataReader.IsDBNull(ordinal) ||
343-
JetConfiguration.IntegerNullValue != null && ((int) JetConfiguration.IntegerNullValue).Equals(GetValue(ordinal));
343+
JetConfiguration.IntegerNullValue != null && ((int)JetConfiguration.IntegerNullValue).Equals(GetValue(ordinal));
344344

345345
public override bool NextResult()
346346
=> _wrappedDataReader.NextResult();
@@ -356,5 +356,23 @@ public override object this[string name]
356356

357357
public override object this[int ordinal]
358358
=> _wrappedDataReader[ordinal];
359+
360+
public override T GetFieldValue<T>(int ordinal)
361+
{
362+
if (typeof(T) == typeof(DateTime))
363+
{
364+
return (T)(object)GetDateTime(ordinal);
365+
}
366+
if (typeof(T) == typeof(TimeSpan))
367+
{
368+
return (T)(object)GetTimeSpan(ordinal);
369+
}
370+
if (typeof(T) == typeof(DateTimeOffset))
371+
{
372+
return (T)(object)GetDateTimeOffset(ordinal);
373+
}
374+
375+
return (T)GetValue(ordinal);
376+
}
359377
}
360378
}

src/EFCore.Jet/Query/ExpressionTranslators/Internal/JetMemberTranslatorProvider.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,13 @@ public JetMemberTranslatorProvider([NotNull] RelationalMemberTranslatorProviderD
1919
: base(dependencies)
2020
{
2121
var sqlExpressionFactory = (JetSqlExpressionFactory)dependencies.SqlExpressionFactory;
22-
22+
2323
// ReSharper disable once VirtualMemberCallInConstructor
2424
AddTranslators(new IMemberTranslator[]
2525
{
2626
new JetStringMemberTranslator(sqlExpressionFactory),
2727
new JetDateTimeMemberTranslator(sqlExpressionFactory),
28+
new JetTimeSpanMemberTranslator(sqlExpressionFactory)
2829
});
2930
}
3031
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Reflection;
7+
using Microsoft.EntityFrameworkCore;
8+
using Microsoft.EntityFrameworkCore.Diagnostics;
9+
using Microsoft.EntityFrameworkCore.Query;
10+
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
11+
12+
namespace EntityFrameworkCore.Jet.Query.ExpressionTranslators.Internal;
13+
14+
/// <summary>
15+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
16+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
17+
/// any release. You should only use it directly in your code with extreme caution and knowing that
18+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
19+
/// </summary>
20+
public class JetTimeSpanMemberTranslator : IMemberTranslator
21+
{
22+
private static readonly Dictionary<string, string> DatePartMappings = new()
23+
{
24+
{ nameof(TimeSpan.Hours), "h" },
25+
{ nameof(TimeSpan.Minutes), "n" },
26+
{ nameof(TimeSpan.Seconds), "s" },
27+
{ nameof(TimeSpan.Milliseconds), "millisecond" }
28+
};
29+
30+
private readonly ISqlExpressionFactory _sqlExpressionFactory;
31+
32+
/// <summary>
33+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
34+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
35+
/// any release. You should only use it directly in your code with extreme caution and knowing that
36+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
37+
/// </summary>
38+
public JetTimeSpanMemberTranslator(ISqlExpressionFactory sqlExpressionFactory)
39+
{
40+
_sqlExpressionFactory = sqlExpressionFactory;
41+
}
42+
43+
/// <summary>
44+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
45+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
46+
/// any release. You should only use it directly in your code with extreme caution and knowing that
47+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
48+
/// </summary>
49+
public virtual SqlExpression? Translate(
50+
SqlExpression? instance,
51+
MemberInfo member,
52+
Type returnType,
53+
IDiagnosticsLogger<DbLoggerCategory.Query> logger)
54+
{
55+
if (member.DeclaringType == typeof(TimeSpan) && DatePartMappings.TryGetValue(member.Name, out var value))
56+
{
57+
return _sqlExpressionFactory.Function(
58+
"DATEPART", new[] { _sqlExpressionFactory.Constant(value), instance! },
59+
nullable: true,
60+
argumentsPropagateNullability: new[] { false, true },
61+
returnType);
62+
}
63+
64+
return null;
65+
}
66+
}

0 commit comments

Comments
 (0)