Skip to content

Commit 00178eb

Browse files
authored
Fix DateTime fractions logic and make milliseconds support opt-in (#86)
* Ensure deterministic test ordering. * Fix DateTime factions logic. * Make milliseconds support opt-in. * Fix logic and add tests. * Fix DateTime related OLE DB parameter configuration.
1 parent 1df41e8 commit 00178eb

26 files changed

+345
-88
lines changed

src/EFCore.Jet.Data/JetDataReader.cs

+19-6
Original file line numberDiff line numberDiff line change
@@ -148,12 +148,25 @@ 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-
return JetConfiguration.UseDefaultValueOnDBNullConversionError &&
152-
Convert.IsDBNull(value)
153-
? default
154-
: value is double doubleValue
155-
? new DateTime(JetConfiguration.TimeSpanOffset.Ticks + (long) (doubleValue * TimeSpan.TicksPerDay))
156-
: (DateTime) value;
151+
152+
if (JetConfiguration.UseDefaultValueOnDBNullConversionError &&
153+
Convert.IsDBNull(value))
154+
return default;
155+
156+
if (value is double doubleValue)
157+
{
158+
// Round to milliseconds.
159+
return new DateTime(
160+
JetConfiguration.TimeSpanOffset.Ticks +
161+
(long) (Math.Round(
162+
(decimal) (long) ((decimal) doubleValue * TimeSpan.TicksPerDay) /
163+
TimeSpan.TicksPerMillisecond,
164+
0,
165+
MidpointRounding.AwayFromZero) *
166+
TimeSpan.TicksPerMillisecond));
167+
}
168+
169+
return (DateTime) value;
157170
}
158171

159172
public virtual TimeSpan GetTimeSpan(int ordinal)

src/EFCore.Jet/Infrastructure/Internal/IJetOptions.cs

+1
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,6 @@ public interface IJetOptions : ISingletonOptions
1414
string ConnectionString { get; }
1515
DataAccessProviderType DataAccessProviderType { get; }
1616
bool UseOuterSelectSkipEmulationViaDataReader { get; }
17+
bool EnableMillisecondsSupport { get; }
1718
}
1819
}

src/EFCore.Jet/Infrastructure/Internal/JetOptionsExtension.cs

+34-1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ public class JetOptionsExtension : RelationalOptionsExtension
2323
// private bool? _rowNumberPaging;
2424
private DbProviderFactory _dataAccessProviderFactory;
2525
private bool _useOuterSelectSkipEmulationViaDataReader;
26+
private bool _enableMillisecondsSupport;
2627

2728
/// <summary>
2829
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -48,6 +49,7 @@ protected JetOptionsExtension([NotNull] JetOptionsExtension copyFrom)
4849
// _rowNumberPaging = copyFrom._rowNumberPaging;
4950
_dataAccessProviderFactory = copyFrom._dataAccessProviderFactory;
5051
_useOuterSelectSkipEmulationViaDataReader = copyFrom._useOuterSelectSkipEmulationViaDataReader;
52+
_enableMillisecondsSupport = copyFrom._enableMillisecondsSupport;
5153
}
5254

5355
/// <summary>
@@ -139,6 +141,29 @@ public virtual JetOptionsExtension WithUseOuterSelectSkipEmulationViaDataReader(
139141
return clone;
140142
}
141143

144+
/// <summary>
145+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
146+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
147+
/// any release. You should only use it directly in your code with extreme caution and knowing that
148+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
149+
/// </summary>
150+
public virtual bool EnableMillisecondsSupport => _enableMillisecondsSupport;
151+
152+
/// <summary>
153+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
154+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
155+
/// any release. You should only use it directly in your code with extreme caution and knowing that
156+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
157+
/// </summary>
158+
public virtual JetOptionsExtension WithEnableMillisecondsSupport(bool enabled)
159+
{
160+
var clone = (JetOptionsExtension) Clone();
161+
162+
clone._enableMillisecondsSupport = enabled;
163+
164+
return clone;
165+
}
166+
142167
/// <summary>
143168
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
144169
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
@@ -190,6 +215,11 @@ public override string LogFragment
190215
builder.Append("UseOuterSelectSkipEmulationViaDataReader ");
191216
}
192217

218+
if (Extension._enableMillisecondsSupport)
219+
{
220+
builder.Append("EnableMillisecondsSupport ");
221+
}
222+
193223
_logFragment = builder.ToString();
194224
}
195225

@@ -203,7 +233,8 @@ public override long GetServiceProviderHashCode()
203233
{
204234
_serviceProviderHash = (base.GetServiceProviderHashCode() * 397) ^
205235
(Extension._dataAccessProviderFactory?.GetHashCode() ?? 0L) ^
206-
(Extension._useOuterSelectSkipEmulationViaDataReader.GetHashCode() * 397) /* ^
236+
(Extension._useOuterSelectSkipEmulationViaDataReader.GetHashCode() * 397) ^
237+
(Extension._enableMillisecondsSupport.GetHashCode() * 397)/* ^
207238
(Extension._rowNumberPaging?.GetHashCode() ?? 0L)*/;
208239
}
209240

@@ -222,6 +253,8 @@ public override void PopulateDebugInfo(IDictionary<string, string> debugInfo)
222253
debugInfo["Jet:" + nameof(JetDbContextOptionsBuilder.UseOuterSelectSkipEmulationViaDataReader)]
223254
#pragma warning restore 618
224255
= Extension._useOuterSelectSkipEmulationViaDataReader.GetHashCode().ToString(CultureInfo.InvariantCulture);
256+
debugInfo["Jet:" + nameof(JetDbContextOptionsBuilder.EnableMillisecondsSupport)]
257+
= Extension._enableMillisecondsSupport.GetHashCode().ToString(CultureInfo.InvariantCulture);
225258
}
226259
}
227260
}

src/EFCore.Jet/Infrastructure/JetDbContextOptionsBuilder.cs

+7
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,13 @@ public JetDbContextOptionsBuilder([NotNull] DbContextOptionsBuilder optionsBuild
4747
public virtual JetDbContextOptionsBuilder UseOuterSelectSkipEmulationViaDataReader(bool enabled = true)
4848
=> WithOption(e => e.WithUseOuterSelectSkipEmulationViaDataReader(enabled));
4949

50+
/// <summary>
51+
/// Configures the context support milliseconds in `DateTime`, `DateTimeOffset` and `TimeSpan` values when
52+
/// accessing Jet databases. Jet has no native support for milliseconds, therefore this feature is opt-in.
53+
/// </summary>
54+
public virtual JetDbContextOptionsBuilder EnableMillisecondsSupport(bool enabled = true)
55+
=> WithOption(e => e.WithEnableMillisecondsSupport(enabled));
56+
5057
/// <summary>
5158
/// Configures the context to use the default retrying <see cref="IExecutionStrategy" />.
5259
/// </summary>

src/EFCore.Jet/Internal/JetOptions.cs

+17
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ public virtual void Initialize(IDbContextOptions options)
2828

2929
DataAccessProviderType = GetDataAccessProviderTypeFromOptions(jetOptions);
3030
UseOuterSelectSkipEmulationViaDataReader = jetOptions.UseOuterSelectSkipEmulationViaDataReader;
31+
EnableMillisecondsSupport = jetOptions.EnableMillisecondsSupport;
3132
ConnectionString = jetOptions.Connection?.ConnectionString ?? jetOptions.ConnectionString;
3233
}
3334

@@ -64,6 +65,14 @@ public virtual void Validate(IDbContextOptions options)
6465
nameof(JetOptionsExtension.UseOuterSelectSkipEmulationViaDataReader),
6566
nameof(DbContextOptionsBuilder.UseInternalServiceProvider)));
6667
}
68+
69+
if (EnableMillisecondsSupport != jetOptions.EnableMillisecondsSupport)
70+
{
71+
throw new InvalidOperationException(
72+
CoreStrings.SingletonOptionChanged(
73+
nameof(JetOptionsExtension.EnableMillisecondsSupport),
74+
nameof(DbContextOptionsBuilder.UseInternalServiceProvider)));
75+
}
6776
}
6877

6978
private static DataAccessProviderType GetDataAccessProviderTypeFromOptions(JetOptionsExtension jetOptions)
@@ -125,6 +134,14 @@ private static DataAccessProviderType GetDataAccessProviderTypeFromOptions(JetOp
125134
/// </summary>
126135
public virtual bool UseOuterSelectSkipEmulationViaDataReader { get; private set; }
127136

137+
/// <summary>
138+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
139+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
140+
/// any release. You should only use it directly in your code with extreme caution and knowing that
141+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
142+
/// </summary>
143+
public bool EnableMillisecondsSupport { get; private set; }
144+
128145
/// <summary>
129146
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
130147
/// the same compatibility standards as public APIs. It may be changed or removed without notice in

src/EFCore.Jet/Migrations/JetMigrationsSqlGenerator.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -773,7 +773,7 @@ protected override void DefaultValue(
773773
}
774774

775775
defaultValue = defaultValue.GetType().IsTimeRelatedType()
776-
? JetDateTimeTypeMapping.GenerateNonNullSqlLiteral(defaultValue, true)
776+
? JetDateTimeTypeMapping.GenerateNonNullSqlLiteral(defaultValue, true, _options.EnableMillisecondsSupport)
777777
: typeMapping.GenerateSqlLiteral(defaultValue);
778778

779779
builder

src/EFCore.Jet/Query/Internal/JetQueryTranslationPostprocessor.cs

+11-2
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,35 @@
11
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
22

33
using System.Linq.Expressions;
4+
using EntityFrameworkCore.Jet.Infrastructure.Internal;
45
using Microsoft.EntityFrameworkCore.Query;
56

67
namespace EntityFrameworkCore.Jet.Query.Internal
78
{
89
public class JetQueryTranslationPostprocessor : RelationalQueryTranslationPostprocessor
910
{
11+
private readonly IJetOptions _options;
12+
1013
public JetQueryTranslationPostprocessor(
1114
QueryTranslationPostprocessorDependencies dependencies,
1215
RelationalQueryTranslationPostprocessorDependencies relationalDependencies,
13-
QueryCompilationContext queryCompilationContext)
16+
QueryCompilationContext queryCompilationContext,
17+
IJetOptions options)
1418
: base(dependencies, relationalDependencies, queryCompilationContext)
1519
{
20+
_options = options;
1621
}
1722

1823
public override Expression Process(Expression query)
1924
{
2025
query = base.Process(query);
2126

2227
query = new SearchConditionConvertingExpressionVisitor(SqlExpressionFactory).Visit(query);
23-
query = new JetDateTimeExpressionVisitor(SqlExpressionFactory).Visit(query);
28+
29+
if (_options.EnableMillisecondsSupport)
30+
{
31+
query = new JetDateTimeExpressionVisitor(SqlExpressionFactory).Visit(query);
32+
}
2433

2534
return query;
2635
}

src/EFCore.Jet/Query/Internal/JetQueryTranslationPostprocessorFactory.cs

+7-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
22

3+
using EntityFrameworkCore.Jet.Infrastructure.Internal;
34
using Microsoft.EntityFrameworkCore.Query;
45
using Microsoft.Extensions.DependencyInjection;
56

@@ -22,19 +23,23 @@ public class JetQueryTranslationPostprocessorFactory : IQueryTranslationPostproc
2223
{
2324
private readonly QueryTranslationPostprocessorDependencies _dependencies;
2425
private readonly RelationalQueryTranslationPostprocessorDependencies _relationalDependencies;
26+
private readonly IJetOptions _options;
2527

2628
public JetQueryTranslationPostprocessorFactory(
2729
QueryTranslationPostprocessorDependencies dependencies,
28-
RelationalQueryTranslationPostprocessorDependencies relationalDependencies)
30+
RelationalQueryTranslationPostprocessorDependencies relationalDependencies,
31+
IJetOptions options)
2932
{
3033
_dependencies = dependencies;
3134
_relationalDependencies = relationalDependencies;
35+
_options = options;
3236
}
3337

3438
public virtual QueryTranslationPostprocessor Create(QueryCompilationContext queryCompilationContext)
3539
=> new JetQueryTranslationPostprocessor(
3640
_dependencies,
3741
_relationalDependencies,
38-
queryCompilationContext);
42+
queryCompilationContext,
43+
_options);
3944
}
4045
}

src/EFCore.Jet/Storage/Internal/JetDateTimeOffsetTypeMapping.cs

+10-4
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,37 @@
22

33
using System;
44
using System.Data.Common;
5+
using EntityFrameworkCore.Jet.Infrastructure.Internal;
56
using JetBrains.Annotations;
67
using Microsoft.EntityFrameworkCore.Storage;
78

89
namespace EntityFrameworkCore.Jet.Storage.Internal
910
{
1011
public class JetDateTimeOffsetTypeMapping : JetDateTimeTypeMapping
1112
{
13+
private readonly IJetOptions _options;
14+
1215
public JetDateTimeOffsetTypeMapping(
13-
[NotNull] string storeType)
16+
[NotNull] string storeType,
17+
[NotNull] IJetOptions options)
1418
: base(
1519
storeType,
20+
options,
1621
System.Data.DbType.DateTime,
1722
typeof(DateTimeOffset)) // delibrately use DbType.DateTime, because OleDb will throw a
1823
// "No mapping exists from DbType DateTimeOffset to a known OleDbType."
1924
// exception when using DbType.DateTimeOffset.
2025
{
26+
_options = options;
2127
}
2228

23-
protected JetDateTimeOffsetTypeMapping(RelationalTypeMappingParameters parameters)
24-
: base(parameters)
29+
protected JetDateTimeOffsetTypeMapping(RelationalTypeMappingParameters parameters, IJetOptions options)
30+
: base(parameters, options)
2531
{
2632
}
2733

2834
protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters)
29-
=> new JetDateTimeOffsetTypeMapping(parameters);
35+
=> new JetDateTimeOffsetTypeMapping(parameters, _options);
3036

3137
protected override void ConfigureParameter(DbParameter parameter)
3238
{

0 commit comments

Comments
 (0)