diff --git a/src/EFCore.Relational/Storage/DateOnlyTypeMapping.cs b/src/EFCore.Relational/Storage/DateOnlyTypeMapping.cs
new file mode 100644
index 00000000000..85d26f19040
--- /dev/null
+++ b/src/EFCore.Relational/Storage/DateOnlyTypeMapping.cs
@@ -0,0 +1,57 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Data;
+
+namespace Microsoft.EntityFrameworkCore.Storage
+{
+ ///
+ ///
+ /// Represents the mapping between a .NET type and a database type.
+ ///
+ ///
+ /// This type is typically used by database providers (and other extensions). It is generally
+ /// not used in application code.
+ ///
+ ///
+ public class DateOnlyTypeMapping : RelationalTypeMapping
+ {
+ private const string DateOnlyFormatConst = @"{0:yyyy-MM-dd}";
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The name of the database type.
+ /// The to be used.
+ public DateOnlyTypeMapping(
+ string storeType,
+ DbType? dbType = null)
+ : base(storeType, typeof(DateOnly), dbType)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Parameter object for .
+ protected DateOnlyTypeMapping(RelationalTypeMappingParameters parameters)
+ : base(parameters)
+ {
+ }
+
+ ///
+ /// Creates a copy of this mapping.
+ ///
+ /// The parameters for this mapping.
+ /// The newly created mapping.
+ protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters)
+ => new DateOnlyTypeMapping(parameters);
+
+ ///
+ /// Gets the string format to be used to generate SQL literals of this type.
+ ///
+ protected override string SqlLiteralFormatString
+ => "DATE '" + DateOnlyFormatConst + "'";
+ }
+}
diff --git a/src/EFCore.Relational/Storage/TimeOnlyTypeMapping.cs b/src/EFCore.Relational/Storage/TimeOnlyTypeMapping.cs
new file mode 100644
index 00000000000..1a0cb5f15ad
--- /dev/null
+++ b/src/EFCore.Relational/Storage/TimeOnlyTypeMapping.cs
@@ -0,0 +1,61 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Data;
+using System.Globalization;
+using Microsoft.EntityFrameworkCore.Utilities;
+
+namespace Microsoft.EntityFrameworkCore.Storage
+{
+ ///
+ ///
+ /// Represents the mapping between a .NET type and a database type.
+ ///
+ ///
+ /// This type is typically used by database providers (and other extensions). It is generally
+ /// not used in application code.
+ ///
+ ///
+ public class TimeOnlyTypeMapping : RelationalTypeMapping
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The name of the database type.
+ /// The to be used.
+ public TimeOnlyTypeMapping(
+ string storeType,
+ DbType? dbType = null)
+ : base(storeType, typeof(TimeOnly), dbType)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Parameter object for .
+ protected TimeOnlyTypeMapping(RelationalTypeMappingParameters parameters)
+ : base(parameters)
+ {
+ }
+
+ ///
+ /// Creates a copy of this mapping.
+ ///
+ /// The parameters for this mapping.
+ /// The newly created mapping.
+ protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters)
+ => new TimeOnlyTypeMapping(parameters);
+
+ ///
+ protected override string GenerateNonNullSqlLiteral(object value)
+ {
+ var timeOnly = (TimeOnly)value;
+
+ return timeOnly.Ticks % TimeSpan.TicksPerSecond == 0
+ ? FormattableString.Invariant($@"TIME '{value:HH\:mm\:ss}'")
+ : FormattableString.Invariant($@"TIME '{value:HH\:mm\:ss\.FFFFFFF}'");
+ }
+ }
+}
diff --git a/src/EFCore.Sqlite.Core/Query/Internal/SqliteDateOnlyMemberTranslator.cs b/src/EFCore.Sqlite.Core/Query/Internal/SqliteDateOnlyMemberTranslator.cs
new file mode 100644
index 00000000000..55eef434a79
--- /dev/null
+++ b/src/EFCore.Sqlite.Core/Query/Internal/SqliteDateOnlyMemberTranslator.cs
@@ -0,0 +1,72 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using Microsoft.EntityFrameworkCore.Diagnostics;
+using Microsoft.EntityFrameworkCore.Query;
+using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
+using Microsoft.EntityFrameworkCore.Utilities;
+
+namespace Microsoft.EntityFrameworkCore.Sqlite.Query.Internal
+{
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public class SqliteDateOnlyMemberTranslator : IMemberTranslator
+ {
+ private static readonly Dictionary _datePartMapping
+ = new()
+ {
+ { nameof(DateOnly.Year), "%Y" },
+ { nameof(DateOnly.Month), "%m" },
+ { nameof(DateOnly.DayOfYear), "%j" },
+ { nameof(DateOnly.Day), "%d" },
+ { nameof(DateOnly.DayOfWeek), "%w" }
+ };
+
+ private readonly ISqlExpressionFactory _sqlExpressionFactory;
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public SqliteDateOnlyMemberTranslator(ISqlExpressionFactory sqlExpressionFactory)
+ {
+ _sqlExpressionFactory = sqlExpressionFactory;
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual SqlExpression? Translate(
+ SqlExpression? instance,
+ MemberInfo member,
+ Type returnType,
+ IDiagnosticsLogger logger)
+ {
+ Check.NotNull(member, nameof(member));
+ Check.NotNull(returnType, nameof(returnType));
+ Check.NotNull(logger, nameof(logger));
+
+ return member.DeclaringType == typeof(DateOnly) && _datePartMapping.TryGetValue(member.Name, out var datePart)
+ ? _sqlExpressionFactory.Convert(
+ SqliteExpression.Strftime(
+ _sqlExpressionFactory,
+ typeof(string),
+ datePart,
+ instance!),
+ returnType)
+ : null;
+ }
+ }
+}
diff --git a/src/EFCore.Sqlite.Core/Query/Internal/SqliteDateTimeAddTranslator.cs b/src/EFCore.Sqlite.Core/Query/Internal/SqliteDateTimeAddTranslator.cs
index acdaa523112..e18be4896b3 100644
--- a/src/EFCore.Sqlite.Core/Query/Internal/SqliteDateTimeAddTranslator.cs
+++ b/src/EFCore.Sqlite.Core/Query/Internal/SqliteDateTimeAddTranslator.cs
@@ -32,7 +32,11 @@ private static readonly MethodInfo _addTicks
{ typeof(DateTime).GetRequiredRuntimeMethod(nameof(DateTime.AddDays), new[] { typeof(double) }), " days" },
{ typeof(DateTime).GetRequiredRuntimeMethod(nameof(DateTime.AddHours), new[] { typeof(double) }), " hours" },
{ typeof(DateTime).GetRequiredRuntimeMethod(nameof(DateTime.AddMinutes), new[] { typeof(double) }), " minutes" },
- { typeof(DateTime).GetRequiredRuntimeMethod(nameof(DateTime.AddSeconds), new[] { typeof(double) }), " seconds" }
+ { typeof(DateTime).GetRequiredRuntimeMethod(nameof(DateTime.AddSeconds), new[] { typeof(double) }), " seconds" },
+
+ { typeof(DateOnly).GetRequiredRuntimeMethod(nameof(DateOnly.AddYears), new[] { typeof(int) }), " years" },
+ { typeof(DateOnly).GetRequiredRuntimeMethod(nameof(DateOnly.AddMonths), new[] { typeof(int) }), " months" },
+ { typeof(DateOnly).GetRequiredRuntimeMethod(nameof(DateOnly.AddDays), new[] { typeof(int) }), " days" },
};
private readonly ISqlExpressionFactory _sqlExpressionFactory;
@@ -64,6 +68,18 @@ public SqliteDateTimeAddTranslator(ISqlExpressionFactory sqlExpressionFactory)
Check.NotNull(arguments, nameof(arguments));
Check.NotNull(logger, nameof(logger));
+ return method.DeclaringType == typeof(DateTime)
+ ? TranslateDateTime(instance, method, arguments)
+ : method.DeclaringType == typeof(DateOnly)
+ ? TranslateDateOnly(instance, method, arguments)
+ : null;
+ }
+
+ private SqlExpression? TranslateDateTime(
+ SqlExpression? instance,
+ MethodInfo method,
+ IReadOnlyList arguments)
+ {
SqlExpression? modifier = null;
if (_addMilliseconds.Equals(method))
{
@@ -122,5 +138,29 @@ public SqliteDateTimeAddTranslator(ISqlExpressionFactory sqlExpressionFactory)
return null;
}
+
+ private SqlExpression? TranslateDateOnly(
+ SqlExpression? instance,
+ MethodInfo method,
+ IReadOnlyList arguments)
+ {
+ if (instance is not null && _methodInfoToUnitSuffix.TryGetValue(method, out var unitSuffix))
+ {
+ return _sqlExpressionFactory.Function(
+ "date",
+ new[]
+ {
+ instance,
+ _sqlExpressionFactory.Add(
+ _sqlExpressionFactory.Convert(arguments[0], typeof(string)),
+ _sqlExpressionFactory.Constant(unitSuffix))
+ },
+ argumentsPropagateNullability: new[] { true, true },
+ nullable: true,
+ returnType: method.ReturnType);
+ }
+
+ return null;
+ }
}
}
diff --git a/src/EFCore.Sqlite.Core/Query/Internal/SqliteMemberTranslatorProvider.cs b/src/EFCore.Sqlite.Core/Query/Internal/SqliteMemberTranslatorProvider.cs
index 0bdebdab542..1280b6a0e3c 100644
--- a/src/EFCore.Sqlite.Core/Query/Internal/SqliteMemberTranslatorProvider.cs
+++ b/src/EFCore.Sqlite.Core/Query/Internal/SqliteMemberTranslatorProvider.cs
@@ -27,7 +27,9 @@ public SqliteMemberTranslatorProvider(RelationalMemberTranslatorProviderDependen
AddTranslators(
new IMemberTranslator[]
{
- new SqliteDateTimeMemberTranslator(sqlExpressionFactory), new SqliteStringLengthTranslator(sqlExpressionFactory)
+ new SqliteDateTimeMemberTranslator(sqlExpressionFactory),
+ new SqliteStringLengthTranslator(sqlExpressionFactory),
+ new SqliteDateOnlyMemberTranslator(sqlExpressionFactory)
});
}
}
diff --git a/src/EFCore.Sqlite.Core/Storage/Internal/SqliteDateOnlyTypeMapping.cs b/src/EFCore.Sqlite.Core/Storage/Internal/SqliteDateOnlyTypeMapping.cs
new file mode 100644
index 00000000000..dbe0ca40abe
--- /dev/null
+++ b/src/EFCore.Sqlite.Core/Storage/Internal/SqliteDateOnlyTypeMapping.cs
@@ -0,0 +1,60 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Data;
+using Microsoft.EntityFrameworkCore.Storage;
+
+namespace Microsoft.EntityFrameworkCore.Sqlite.Storage.Internal
+{
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public class SqliteDateOnlyTypeMapping : DateOnlyTypeMapping
+ {
+ private const string DateOnlyFormatConst = @"'{0:yyyy\-MM\-dd}'";
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public SqliteDateOnlyTypeMapping(
+ string storeType,
+ DbType? dbType = null)
+ : base(storeType, dbType)
+ {
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ protected SqliteDateOnlyTypeMapping(RelationalTypeMappingParameters parameters)
+ : base(parameters)
+ {
+ }
+
+ ///
+ /// Creates a copy of this mapping.
+ ///
+ /// The parameters for this mapping.
+ /// The newly created mapping.
+ protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters)
+ => new SqliteDateOnlyTypeMapping(parameters);
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ protected override string SqlLiteralFormatString
+ => DateOnlyFormatConst;
+ }
+}
diff --git a/src/EFCore.Sqlite.Core/Storage/Internal/SqliteTimeOnlyTypeMapping.cs b/src/EFCore.Sqlite.Core/Storage/Internal/SqliteTimeOnlyTypeMapping.cs
new file mode 100644
index 00000000000..aada79a4110
--- /dev/null
+++ b/src/EFCore.Sqlite.Core/Storage/Internal/SqliteTimeOnlyTypeMapping.cs
@@ -0,0 +1,60 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Data;
+using Microsoft.EntityFrameworkCore.Storage;
+
+namespace Microsoft.EntityFrameworkCore.Sqlite.Storage.Internal
+{
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public class SqliteTimeOnlyTypeMapping : TimeOnlyTypeMapping
+ {
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public SqliteTimeOnlyTypeMapping(
+ string storeType,
+ DbType? dbType = null)
+ : base(storeType, dbType)
+ {
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ protected SqliteTimeOnlyTypeMapping(RelationalTypeMappingParameters parameters)
+ : base(parameters)
+ {
+ }
+
+ ///
+ /// Creates a copy of this mapping.
+ ///
+ /// The parameters for this mapping.
+ /// The newly created mapping.
+ protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters)
+ => new SqliteTimeOnlyTypeMapping(parameters);
+
+ ///
+ protected override string GenerateNonNullSqlLiteral(object value)
+ {
+ var timeOnly = (TimeOnly)value;
+
+ return timeOnly.Ticks % TimeSpan.TicksPerSecond == 0
+ ? FormattableString.Invariant($@"'{value:HH\:mm\:ss}'")
+ : FormattableString.Invariant($@"'{value:HH\:mm\:ss\.fffffff}'");
+ }
+ }
+}
diff --git a/src/EFCore.Sqlite.Core/Storage/Internal/SqliteTypeMappingSource.cs b/src/EFCore.Sqlite.Core/Storage/Internal/SqliteTypeMappingSource.cs
index e640b3fd259..efc6cc10bac 100644
--- a/src/EFCore.Sqlite.Core/Storage/Internal/SqliteTypeMappingSource.cs
+++ b/src/EFCore.Sqlite.Core/Storage/Internal/SqliteTypeMappingSource.cs
@@ -88,6 +88,8 @@ private static readonly HashSet _spatialiteTypes
{ typeof(DateTime), new SqliteDateTimeTypeMapping(TextTypeName) },
{ typeof(DateTimeOffset), new SqliteDateTimeOffsetTypeMapping(TextTypeName) },
{ typeof(TimeSpan), new TimeSpanTypeMapping(TextTypeName) },
+ { typeof(DateOnly), new SqliteDateOnlyTypeMapping(TextTypeName) },
+ { typeof(TimeOnly), new SqliteTimeOnlyTypeMapping(TextTypeName) },
{ typeof(decimal), new SqliteDecimalTypeMapping(TextTypeName) },
{ typeof(double), _real },
{ typeof(float), new FloatTypeMapping(RealTypeName) },
diff --git a/src/Microsoft.Data.Sqlite.Core/SqliteValueBinder.cs b/src/Microsoft.Data.Sqlite.Core/SqliteValueBinder.cs
index 52201522ab7..090be6f965b 100644
--- a/src/Microsoft.Data.Sqlite.Core/SqliteValueBinder.cs
+++ b/src/Microsoft.Data.Sqlite.Core/SqliteValueBinder.cs
@@ -112,6 +112,38 @@ public virtual void Bind()
BindText(value);
}
}
+#if NET6_0_OR_GREATER
+ else if (type == typeof(DateOnly))
+ {
+ var dateOnly = (DateOnly)_value;
+ if (_sqliteType == SqliteType.Real)
+ {
+ var value = ToJulianDate(dateOnly.Year, dateOnly.Month, dateOnly.Day, 0, 0, 0, 0);
+ BindDouble(value);
+ }
+ else
+ {
+ var value = dateOnly.ToString(@"yyyy\-MM\-dd", CultureInfo.InvariantCulture);
+ BindText(value);
+ }
+ }
+ else if (type == typeof(TimeOnly))
+ {
+ var timeOnly = (TimeOnly)_value;
+ if (_sqliteType == SqliteType.Real)
+ {
+ var value = GetTotalDays(timeOnly.Hour, timeOnly.Minute, timeOnly.Second, timeOnly.Millisecond);
+ BindDouble(value);
+ }
+ else
+ {
+ var value = timeOnly.Ticks % 10000000 == 0
+ ? timeOnly.ToString(@"HH:mm:ss", CultureInfo.InvariantCulture)
+ : timeOnly.ToString(@"HH:mm:ss.fffffff", CultureInfo.InvariantCulture);
+ BindText(value);
+ }
+ }
+#endif
else if (type == typeof(DBNull))
{
BindNull();
@@ -247,11 +279,15 @@ internal static SqliteType GetSqliteType(object? value)
}
private static double ToJulianDate(DateTime dateTime)
+ => ToJulianDate(
+ dateTime.Year, dateTime.Month, dateTime.Day, dateTime.Hour, dateTime.Minute, dateTime.Second, dateTime.Millisecond);
+
+ private static double ToJulianDate(int year, int month, int day, int hour, int minute, int second, int millisecond)
{
// computeJD
- var Y = dateTime.Year;
- var M = dateTime.Month;
- var D = dateTime.Day;
+ var Y = year;
+ var M = month;
+ var D = day;
if (M <= 2)
{
@@ -265,7 +301,14 @@ private static double ToJulianDate(DateTime dateTime)
var X2 = 306001 * (M + 1) / 10000;
var iJD = (long)((X1 + X2 + D + B - 1524.5) * 86400000);
- iJD += dateTime.Hour * 3600000 + dateTime.Minute * 60000 + (long)((dateTime.Second + dateTime.Millisecond / 1000.0) * 1000);
+ iJD += hour * 3600000 + minute * 60000 + (long)((second + millisecond / 1000.0) * 1000);
+
+ return iJD / 86400000.0;
+ }
+
+ private static double GetTotalDays(int hour, int minute, int second, int millisecond)
+ {
+ var iJD = hour * 3600000 + minute * 60000 + (long)((second + millisecond / 1000.0) * 1000);
return iJD / 86400000.0;
}
diff --git a/src/Microsoft.Data.Sqlite.Core/SqliteValueReader.cs b/src/Microsoft.Data.Sqlite.Core/SqliteValueReader.cs
index efaacfe4b95..d53862f4690 100644
--- a/src/Microsoft.Data.Sqlite.Core/SqliteValueReader.cs
+++ b/src/Microsoft.Data.Sqlite.Core/SqliteValueReader.cs
@@ -68,6 +68,25 @@ public virtual DateTimeOffset GetDateTimeOffset(int ordinal)
}
}
+#if NET6_0_OR_GREATER
+ public virtual DateOnly GetDateOnly(int ordinal)
+ {
+ var sqliteType = GetSqliteType(ordinal);
+ switch (sqliteType)
+ {
+ case SQLITE_FLOAT:
+ case SQLITE_INTEGER:
+ return DateOnly.FromDateTime(FromJulianDate(GetDouble(ordinal)));
+
+ default:
+ return DateOnly.Parse(GetString(ordinal), CultureInfo.InvariantCulture);
+ }
+ }
+
+ public virtual TimeOnly GetTimeOnly(int ordinal)
+ => TimeOnly.Parse(GetString(ordinal), CultureInfo.InvariantCulture);
+#endif
+
public virtual decimal GetDecimal(int ordinal)
=> decimal.Parse(GetString(ordinal), NumberStyles.Number | NumberStyles.AllowExponent, CultureInfo.InvariantCulture);
@@ -169,6 +188,18 @@ public virtual string GetString(int ordinal)
return (T)(object)GetDateTimeOffset(ordinal);
}
+#if NET6_0_OR_GREATER
+ if (type == typeof(DateOnly))
+ {
+ return (T)(object)GetDateOnly(ordinal);
+ }
+
+ if (type == typeof(TimeOnly))
+ {
+ return (T)(object)GetTimeOnly(ordinal);
+ }
+#endif
+
if (type == typeof(DBNull))
{
// NB: NULL values handled above
diff --git a/test/EFCore.OData.FunctionalTests/Query/GearsOfWarODataContext.cs b/test/EFCore.OData.FunctionalTests/Query/GearsOfWarODataContext.cs
index a187fd36638..34742e9fff0 100644
--- a/test/EFCore.OData.FunctionalTests/Query/GearsOfWarODataContext.cs
+++ b/test/EFCore.OData.FunctionalTests/Query/GearsOfWarODataContext.cs
@@ -92,6 +92,14 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
modelBuilder.Entity().Property(l => l.Id).ValueGeneratedNever();
modelBuilder.Entity().Property(g => g.Location).HasColumnType("varchar(100)");
+
+ // No support yet for DateOnly/TimeOnly (#24507)
+ modelBuilder.Entity(
+ b =>
+ {
+ b.Ignore(m => m.Date);
+ b.Ignore(m => m.Time);
+ });
}
}
}
diff --git a/test/EFCore.Relational.Tests/Storage/RelationalTypeMappingTest.cs b/test/EFCore.Relational.Tests/Storage/RelationalTypeMappingTest.cs
index 9005c2ac2d6..6204e0f9fe8 100644
--- a/test/EFCore.Relational.Tests/Storage/RelationalTypeMappingTest.cs
+++ b/test/EFCore.Relational.Tests/Storage/RelationalTypeMappingTest.cs
@@ -406,6 +406,33 @@ public virtual void DateTime_literal_generated_correctly()
"TIMESTAMP '2015-03-12 13:36:37.3710000'");
}
+ [ConditionalFact]
+ public virtual void DateOnly_literal_generated_correctly()
+ {
+ Test_GenerateSqlLiteral_helper(
+ new DateOnlyTypeMapping("DateOnly"),
+ new DateOnly(2015, 3, 12),
+ "DATE '2015-03-12'");
+ }
+
+ [ConditionalFact]
+ public virtual void TimeOnly_literal_generated_correctly()
+ {
+ Test_GenerateSqlLiteral_helper(
+ new TimeOnlyTypeMapping("TimeOnly"),
+ new TimeOnly(13, 10, 15),
+ "TIME '13:10:15'");
+ }
+
+ [ConditionalFact]
+ public virtual void TimeOnly_literal_generated_correctly_with_milliseconds()
+ {
+ Test_GenerateSqlLiteral_helper(
+ new TimeOnlyTypeMapping("TimeOnly"),
+ new TimeOnly(13, 10, 15, 500),
+ "TIME '13:10:15.5'");
+ }
+
[ConditionalFact]
public virtual void Decimal_literal_generated_correctly()
{
diff --git a/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs
index 2831ed4c726..ddee8f46164 100644
--- a/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs
+++ b/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs
@@ -8796,6 +8796,176 @@ public virtual Task Correlated_collection_after_distinct_3_levels_without_origin
});
}
+ [ConditionalTheory]
+ [MemberData(nameof(IsAsyncData))]
+ public virtual Task Where_DateOnly_Year(bool async)
+ {
+ return AssertQuery(
+ async,
+ ss => ss.Set().Where(m => m.Date.Year == 1990).AsTracking(),
+ entryCount: 1);
+ }
+
+ [ConditionalTheory]
+ [MemberData(nameof(IsAsyncData))]
+ public virtual Task Where_DateOnly_Month(bool async)
+ {
+ return AssertQuery(
+ async,
+ ss => ss.Set().Where(m => m.Date.Month == 11).AsTracking(),
+ entryCount: 1);
+ }
+
+ [ConditionalTheory]
+ [MemberData(nameof(IsAsyncData))]
+ public virtual Task Where_DateOnly_Day(bool async)
+ {
+ return AssertQuery(
+ async,
+ ss => ss.Set().Where(m => m.Date.Day == 10).AsTracking(),
+ entryCount: 1);
+ }
+
+ [ConditionalTheory]
+ [MemberData(nameof(IsAsyncData))]
+ public virtual Task Where_DateOnly_DayOfYear(bool async)
+ {
+ return AssertQuery(
+ async,
+ ss => ss.Set().Where(m => m.Date.DayOfYear == 314).AsTracking(),
+ entryCount: 1);
+ }
+
+ [ConditionalTheory]
+ [MemberData(nameof(IsAsyncData))]
+ public virtual Task Where_DateOnly_DayOfWeek(bool async)
+ {
+ return AssertQuery(
+ async,
+ ss => ss.Set().Where(m => m.Date.DayOfWeek == DayOfWeek.Saturday).AsTracking(),
+ entryCount: 1);
+ }
+
+ [ConditionalTheory]
+ [MemberData(nameof(IsAsyncData))]
+ public virtual Task Where_DateOnly_AddYears(bool async)
+ {
+ return AssertQuery(
+ async,
+ ss => ss.Set().Where(m => m.Date.AddYears(3) == new DateOnly(1993, 11, 10)).AsTracking(),
+ entryCount: 1);
+ }
+
+ [ConditionalTheory]
+ [MemberData(nameof(IsAsyncData))]
+ public virtual Task Where_DateOnly_AddMonths(bool async)
+ {
+ return AssertQuery(
+ async,
+ ss => ss.Set().Where(m => m.Date.AddMonths(3) == new DateOnly(1991, 2, 10)).AsTracking(),
+ entryCount: 1);
+ }
+
+ [ConditionalTheory]
+ [MemberData(nameof(IsAsyncData))]
+ public virtual Task Where_DateOnly_AddDays(bool async)
+ {
+ return AssertQuery(
+ async,
+ ss => ss.Set().Where(m => m.Date.AddDays(3) == new DateOnly(1990, 11, 13)).AsTracking(),
+ entryCount: 1);
+ }
+
+ [ConditionalTheory]
+ [MemberData(nameof(IsAsyncData))]
+ public virtual Task Where_TimeOnly_Hour(bool async)
+ {
+ return AssertQuery(
+ async,
+ ss => ss.Set().Where(m => m.Time.Hour == 10).AsTracking(),
+ entryCount: 1);
+ }
+
+ [ConditionalTheory]
+ [MemberData(nameof(IsAsyncData))]
+ public virtual Task Where_TimeOnly_Minute(bool async)
+ {
+ return AssertQuery(
+ async,
+ ss => ss.Set().Where(m => m.Time.Minute == 15).AsTracking(),
+ entryCount: 1);
+ }
+
+ [ConditionalTheory]
+ [MemberData(nameof(IsAsyncData))]
+ public virtual Task Where_TimeOnly_Second(bool async)
+ {
+ return AssertQuery(
+ async,
+ ss => ss.Set().Where(m => m.Time.Second == 50).AsTracking(),
+ entryCount: 1);
+ }
+
+ [ConditionalTheory]
+ [MemberData(nameof(IsAsyncData))]
+ public virtual Task Where_TimeOnly_Millisecond(bool async)
+ {
+ return AssertQuery(
+ async,
+ ss => ss.Set().Where(m => m.Time.Millisecond == 500).AsTracking(),
+ entryCount: 1);
+ }
+
+ [ConditionalTheory]
+ [MemberData(nameof(IsAsyncData))]
+ public virtual Task Where_TimeOnly_AddHours(bool async)
+ {
+ return AssertQuery(
+ async,
+ ss => ss.Set().Where(m => m.Time.AddHours(3) == new TimeOnly(13, 15, 50, 500)).AsTracking(),
+ entryCount: 1);
+ }
+
+ [ConditionalTheory]
+ [MemberData(nameof(IsAsyncData))]
+ public virtual Task Where_TimeOnly_AddMinutes(bool async)
+ {
+ return AssertQuery(
+ async,
+ ss => ss.Set().Where(m => m.Time.AddMinutes(3) == new TimeOnly(10, 18, 50, 500)).AsTracking(),
+ entryCount: 1);
+ }
+
+ [ConditionalTheory]
+ [MemberData(nameof(IsAsyncData))]
+ public virtual Task Where_TimeOnly_Add_TimeSpan(bool async)
+ {
+ return AssertQuery(
+ async,
+ ss => ss.Set().Where(m => m.Time.Add(new TimeSpan(3, 0, 0)) == new TimeOnly(13, 15, 50, 500)).AsTracking(),
+ entryCount: 1);
+ }
+
+ [ConditionalTheory]
+ [MemberData(nameof(IsAsyncData))]
+ public virtual Task Where_TimeOnly_IsBetween(bool async)
+ {
+ return AssertQuery(
+ async,
+ ss => ss.Set().Where(m => m.Time.IsBetween(new TimeOnly(10, 0, 0), new TimeOnly(11, 0, 0))).AsTracking(),
+ entryCount: 1);
+ }
+
+ [ConditionalTheory]
+ [MemberData(nameof(IsAsyncData))]
+ public virtual Task Where_TimeOnly_subtract_TimeOnly(bool async)
+ {
+ return AssertQuery(
+ async,
+ ss => ss.Set().Where(m => m.Time - new TimeOnly(10, 0, 0) == new TimeSpan(0, 0, 15, 50, 500)).AsTracking(),
+ entryCount: 1);
+ }
+
protected GearsOfWarContext CreateContext()
=> Fixture.CreateContext();
diff --git a/test/EFCore.Specification.Tests/TestModels/GearsOfWarModel/GearsOfWarData.cs b/test/EFCore.Specification.Tests/TestModels/GearsOfWarModel/GearsOfWarData.cs
index ccf9da82e3c..d5a28acc1cb 100644
--- a/test/EFCore.Specification.Tests/TestModels/GearsOfWarModel/GearsOfWarData.cs
+++ b/test/EFCore.Specification.Tests/TestModels/GearsOfWarModel/GearsOfWarData.cs
@@ -132,7 +132,9 @@ public static IReadOnlyList CreateMissions()
CodeName = "Lightmass Offensive",
Rating = 2.1,
Timeline = new DateTimeOffset(599898024001234567, new TimeSpan(1, 30, 0)),
- Duration = new TimeSpan(1, 2, 3)
+ Duration = new TimeSpan(1, 2, 3),
+ Date = new DateOnly(2020, 1, 1),
+ Time = new TimeOnly(15, 30, 10)
},
new()
{
@@ -140,7 +142,9 @@ public static IReadOnlyList CreateMissions()
CodeName = "Hollow Storm",
Rating = 4.2,
Timeline = new DateTimeOffset(2, 3, 1, 8, 0, 0, new TimeSpan(-5, 0, 0)),
- Duration = new TimeSpan(0, 1, 2, 3, 456)
+ Duration = new TimeSpan(0, 1, 2, 3, 456),
+ Date = new DateOnly(1990, 11, 10),
+ Time = new TimeOnly(10, 15, 50, 500)
},
new()
{
@@ -148,7 +152,9 @@ public static IReadOnlyList CreateMissions()
CodeName = "Halvo Bay defense",
Rating = null,
Timeline = new DateTimeOffset(10, 5, 3, 12, 0, 0, new TimeSpan()),
- Duration = new TimeSpan(0, 1, 0, 15, 456)
+ Duration = new TimeSpan(0, 1, 0, 15, 456),
+ Date = new DateOnly(1, 1, 1),
+ Time = new TimeOnly(0, 0, 0)
}
};
diff --git a/test/EFCore.Specification.Tests/TestModels/GearsOfWarModel/Mission.cs b/test/EFCore.Specification.Tests/TestModels/GearsOfWarModel/Mission.cs
index 24cd8a53ed2..45a447a86be 100644
--- a/test/EFCore.Specification.Tests/TestModels/GearsOfWarModel/Mission.cs
+++ b/test/EFCore.Specification.Tests/TestModels/GearsOfWarModel/Mission.cs
@@ -14,6 +14,8 @@ public class Mission
public double? Rating { get; set; }
public DateTimeOffset Timeline { get; set; }
public TimeSpan Duration { get; set; }
+ public DateOnly Date { get; set; }
+ public TimeOnly Time { get; set; }
public virtual ICollection ParticipatingSquads { get; set; }
}
diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerFixture.cs b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerFixture.cs
index a66cd5abbde..52359b2ccaf 100644
--- a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerFixture.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerFixture.cs
@@ -17,13 +17,21 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con
modelBuilder.Entity().Property(g => g.Location).HasColumnType("varchar(100)");
- // Full-text binary search
- modelBuilder.Entity()
- .Property("BriefingDocument");
+ modelBuilder.Entity(
+ b =>
+ {
+ // Full-text binary search
+ b.Property("BriefingDocument");
+ b.Property("BriefingDocumentFileExtension").HasColumnType("nvarchar(16)");
+ });
- modelBuilder.Entity()
- .Property("BriefingDocumentFileExtension")
- .HasColumnType("nvarchar(16)");
+ // No support yet for DateOnly/TimeOnly (#24507)
+ modelBuilder.Entity(
+ b =>
+ {
+ b.Ignore(m => m.Date);
+ b.Ignore(m => m.Time);
+ });
}
protected override void Seed(GearsOfWarContext context)
diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs
index 52bbfcc19db..de2ae245517 100644
--- a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs
@@ -7897,7 +7897,7 @@ FROM [Weapons] AS [w]
) AS [t]
ORDER BY [g].[Nickname], [g].[SquadId], [t].[IsAutomatic], [t].[Name]");
}
-
+
public override async Task Correlated_collection_via_SelectMany_with_Distinct_missing_indentifying_columns_in_projection(bool async)
{
await base.Correlated_collection_via_SelectMany_with_Distinct_missing_indentifying_columns_in_projection(async);
@@ -7968,6 +7968,159 @@ FROM [Weapons] AS [w]
ORDER BY [t].[Length], [t2].[HasSoulPatch], [t2].[CityOfBirthName], [t2].[Id]");
}
+ [ConditionalTheory(Skip = "#24507")]
+ [MemberData(nameof(IsAsyncData))]
+ public override async Task Where_DateOnly_Year(bool async)
+ {
+ await base.Where_DateOnly_Year(async);
+
+ AssertSql("");
+ }
+
+ [ConditionalTheory(Skip = "#24507")]
+ [MemberData(nameof(IsAsyncData))]
+ public override async Task Where_DateOnly_Month(bool async)
+ {
+ await base.Where_DateOnly_Month(async);
+
+ AssertSql("");
+ }
+
+ [ConditionalTheory(Skip = "#24507")]
+ [MemberData(nameof(IsAsyncData))]
+ public override async Task Where_DateOnly_Day(bool async)
+ {
+ await base.Where_DateOnly_Day(async);
+
+ AssertSql("");
+ }
+
+ [ConditionalTheory(Skip = "#24507")]
+ [MemberData(nameof(IsAsyncData))]
+ public override async Task Where_DateOnly_DayOfYear(bool async)
+ {
+ await base.Where_DateOnly_DayOfYear(async);
+
+ AssertSql("");
+ }
+
+ [ConditionalTheory(Skip = "#24507")]
+ [MemberData(nameof(IsAsyncData))]
+ public override async Task Where_DateOnly_DayOfWeek(bool async)
+ {
+ await base.Where_DateOnly_DayOfWeek(async);
+
+ AssertSql("");
+ }
+
+ [ConditionalTheory(Skip = "#24507")]
+ [MemberData(nameof(IsAsyncData))]
+ public override async Task Where_DateOnly_AddYears(bool async)
+ {
+ await base.Where_DateOnly_AddYears(async);
+
+ AssertSql("");
+ }
+
+ [ConditionalTheory(Skip = "#24507")]
+ [MemberData(nameof(IsAsyncData))]
+ public override async Task Where_DateOnly_AddMonths(bool async)
+ {
+ await base.Where_DateOnly_AddMonths(async);
+
+ AssertSql("");
+ }
+
+ [ConditionalTheory(Skip = "#24507")]
+ [MemberData(nameof(IsAsyncData))]
+ public override async Task Where_DateOnly_AddDays(bool async)
+ {
+ await base.Where_DateOnly_AddDays(async);
+
+ AssertSql("");
+ }
+
+ [ConditionalTheory(Skip = "#24507")]
+ [MemberData(nameof(IsAsyncData))]
+ public override async Task Where_TimeOnly_Hour(bool async)
+ {
+ await base.Where_TimeOnly_Hour(async);
+
+ AssertSql("");
+ }
+
+ [ConditionalTheory(Skip = "#24507")]
+ [MemberData(nameof(IsAsyncData))]
+ public override async Task Where_TimeOnly_Minute(bool async)
+ {
+ await base.Where_TimeOnly_Minute(async);
+
+ AssertSql("");
+ }
+
+ [ConditionalTheory(Skip = "#24507")]
+ [MemberData(nameof(IsAsyncData))]
+ public override async Task Where_TimeOnly_Second(bool async)
+ {
+ await base.Where_TimeOnly_Second(async);
+
+ AssertSql("");
+ }
+
+ [ConditionalTheory(Skip = "#24507")]
+ [MemberData(nameof(IsAsyncData))]
+ public override async Task Where_TimeOnly_Millisecond(bool async)
+ {
+ await base.Where_TimeOnly_Millisecond(async);
+
+ AssertSql("");
+ }
+
+ [ConditionalTheory(Skip = "#24507")]
+ [MemberData(nameof(IsAsyncData))]
+ public override async Task Where_TimeOnly_AddHours(bool async)
+ {
+ await base.Where_TimeOnly_AddHours(async);
+
+ AssertSql("");
+ }
+
+ [ConditionalTheory(Skip = "#24507")]
+ [MemberData(nameof(IsAsyncData))]
+ public override async Task Where_TimeOnly_AddMinutes(bool async)
+ {
+ await base.Where_TimeOnly_AddMinutes(async);
+
+ AssertSql("");
+ }
+
+ [ConditionalTheory(Skip = "#24507")]
+ [MemberData(nameof(IsAsyncData))]
+ public override async Task Where_TimeOnly_Add_TimeSpan(bool async)
+ {
+ await base.Where_TimeOnly_Add_TimeSpan(async);
+
+ AssertSql("");
+ }
+
+ [ConditionalTheory(Skip = "#24507")]
+ [MemberData(nameof(IsAsyncData))]
+ public override async Task Where_TimeOnly_IsBetween(bool async)
+ {
+ await base.Where_TimeOnly_IsBetween(async);
+
+ AssertSql("");
+ }
+
+ [ConditionalTheory(Skip = "#24507")]
+ [MemberData(nameof(IsAsyncData))]
+ public override async Task Where_TimeOnly_subtract_TimeOnly(bool async)
+ {
+ await base.Where_TimeOnly_subtract_TimeOnly(async);
+
+ AssertSql("");
+ }
+
private void AssertSql(params string[] expected)
=> Fixture.TestSqlLoggerFactory.AssertBaseline(expected);
}
diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerFixture.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerFixture.cs
index 9791db25aca..03039c5c131 100644
--- a/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerFixture.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerFixture.cs
@@ -16,6 +16,14 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con
base.OnModelCreating(modelBuilder, context);
modelBuilder.Entity().Property(g => g.Location).HasColumnType("varchar(100)");
+
+ // No support yet for DateOnly/TimeOnly (#24507)
+ modelBuilder.Entity(
+ b =>
+ {
+ b.Ignore(m => m.Date);
+ b.Ignore(m => m.Time);
+ });
}
}
}
diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs
index a1081c42240..355de145da9 100644
--- a/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs
@@ -8912,6 +8912,159 @@ ELSE NULL
END, [t].[Note]");
}
+ [ConditionalTheory(Skip = "#24507")]
+ [MemberData(nameof(IsAsyncData))]
+ public override async Task Where_DateOnly_Year(bool async)
+ {
+ await base.Where_DateOnly_Year(async);
+
+ AssertSql("");
+ }
+
+ [ConditionalTheory(Skip = "#24507")]
+ [MemberData(nameof(IsAsyncData))]
+ public override async Task Where_DateOnly_Month(bool async)
+ {
+ await base.Where_DateOnly_Month(async);
+
+ AssertSql("");
+ }
+
+ [ConditionalTheory(Skip = "#24507")]
+ [MemberData(nameof(IsAsyncData))]
+ public override async Task Where_DateOnly_Day(bool async)
+ {
+ await base.Where_DateOnly_Day(async);
+
+ AssertSql("");
+ }
+
+ [ConditionalTheory(Skip = "#24507")]
+ [MemberData(nameof(IsAsyncData))]
+ public override async Task Where_DateOnly_DayOfYear(bool async)
+ {
+ await base.Where_DateOnly_DayOfYear(async);
+
+ AssertSql("");
+ }
+
+ [ConditionalTheory(Skip = "#24507")]
+ [MemberData(nameof(IsAsyncData))]
+ public override async Task Where_DateOnly_DayOfWeek(bool async)
+ {
+ await base.Where_DateOnly_DayOfWeek(async);
+
+ AssertSql("");
+ }
+
+ [ConditionalTheory(Skip = "#24507")]
+ [MemberData(nameof(IsAsyncData))]
+ public override async Task Where_DateOnly_AddYears(bool async)
+ {
+ await base.Where_DateOnly_AddYears(async);
+
+ AssertSql("");
+ }
+
+ [ConditionalTheory(Skip = "#24507")]
+ [MemberData(nameof(IsAsyncData))]
+ public override async Task Where_DateOnly_AddMonths(bool async)
+ {
+ await base.Where_DateOnly_AddMonths(async);
+
+ AssertSql("");
+ }
+
+ [ConditionalTheory(Skip = "#24507")]
+ [MemberData(nameof(IsAsyncData))]
+ public override async Task Where_DateOnly_AddDays(bool async)
+ {
+ await base.Where_DateOnly_AddDays(async);
+
+ AssertSql("");
+ }
+
+ [ConditionalTheory(Skip = "#24507")]
+ [MemberData(nameof(IsAsyncData))]
+ public override async Task Where_TimeOnly_Hour(bool async)
+ {
+ await base.Where_TimeOnly_Hour(async);
+
+ AssertSql("");
+ }
+
+ [ConditionalTheory(Skip = "#24507")]
+ [MemberData(nameof(IsAsyncData))]
+ public override async Task Where_TimeOnly_Minute(bool async)
+ {
+ await base.Where_TimeOnly_Minute(async);
+
+ AssertSql("");
+ }
+
+ [ConditionalTheory(Skip = "#24507")]
+ [MemberData(nameof(IsAsyncData))]
+ public override async Task Where_TimeOnly_Second(bool async)
+ {
+ await base.Where_TimeOnly_Second(async);
+
+ AssertSql("");
+ }
+
+ [ConditionalTheory(Skip = "#24507")]
+ [MemberData(nameof(IsAsyncData))]
+ public override async Task Where_TimeOnly_Millisecond(bool async)
+ {
+ await base.Where_TimeOnly_Millisecond(async);
+
+ AssertSql("");
+ }
+
+ [ConditionalTheory(Skip = "#24507")]
+ [MemberData(nameof(IsAsyncData))]
+ public override async Task Where_TimeOnly_AddHours(bool async)
+ {
+ await base.Where_TimeOnly_AddHours(async);
+
+ AssertSql("");
+ }
+
+ [ConditionalTheory(Skip = "#24507")]
+ [MemberData(nameof(IsAsyncData))]
+ public override async Task Where_TimeOnly_AddMinutes(bool async)
+ {
+ await base.Where_TimeOnly_AddMinutes(async);
+
+ AssertSql("");
+ }
+
+ [ConditionalTheory(Skip = "#24507")]
+ [MemberData(nameof(IsAsyncData))]
+ public override async Task Where_TimeOnly_Add_TimeSpan(bool async)
+ {
+ await base.Where_TimeOnly_Add_TimeSpan(async);
+
+ AssertSql("");
+ }
+
+ [ConditionalTheory(Skip = "#24507")]
+ [MemberData(nameof(IsAsyncData))]
+ public override async Task Where_TimeOnly_IsBetween(bool async)
+ {
+ await base.Where_TimeOnly_IsBetween(async);
+
+ AssertSql("");
+ }
+
+ [ConditionalTheory(Skip = "#24507")]
+ [MemberData(nameof(IsAsyncData))]
+ public override async Task Where_TimeOnly_subtract_TimeOnly(bool async)
+ {
+ await base.Where_TimeOnly_subtract_TimeOnly(async);
+
+ AssertSql("");
+ }
+
private void AssertSql(params string[] expected)
=> Fixture.TestSqlLoggerFactory.AssertBaseline(expected);
}
diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs
index 23b9d99d2d2..795c24b83e2 100644
--- a/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs
+++ b/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs
@@ -341,6 +341,183 @@ public override Task Array_access_on_byte_array(bool async)
return base.Array_access_on_byte_array(async);
}
+ [ConditionalTheory]
+ [MemberData(nameof(IsAsyncData))]
+ public override async Task Where_DateOnly_Year(bool async)
+ {
+ await base.Where_DateOnly_Year(async);
+
+ AssertSql(
+ @"SELECT ""m"".""Id"", ""m"".""CodeName"", ""m"".""Date"", ""m"".""Duration"", ""m"".""Rating"", ""m"".""Time"", ""m"".""Timeline""
+FROM ""Missions"" AS ""m""
+WHERE CAST(strftime('%Y', ""m"".""Date"") AS INTEGER) = 1990");
+ }
+
+ [ConditionalTheory]
+ [MemberData(nameof(IsAsyncData))]
+ public override async Task Where_DateOnly_Month(bool async)
+ {
+ await base.Where_DateOnly_Month(async);
+
+ AssertSql(
+ @"SELECT ""m"".""Id"", ""m"".""CodeName"", ""m"".""Date"", ""m"".""Duration"", ""m"".""Rating"", ""m"".""Time"", ""m"".""Timeline""
+FROM ""Missions"" AS ""m""
+WHERE CAST(strftime('%m', ""m"".""Date"") AS INTEGER) = 11");
+ }
+
+ [ConditionalTheory]
+ [MemberData(nameof(IsAsyncData))]
+ public override async Task Where_DateOnly_Day(bool async)
+ {
+ await base.Where_DateOnly_Day(async);
+
+ AssertSql(
+ @"SELECT ""m"".""Id"", ""m"".""CodeName"", ""m"".""Date"", ""m"".""Duration"", ""m"".""Rating"", ""m"".""Time"", ""m"".""Timeline""
+FROM ""Missions"" AS ""m""
+WHERE CAST(strftime('%d', ""m"".""Date"") AS INTEGER) = 10");
+ }
+
+ [ConditionalTheory]
+ [MemberData(nameof(IsAsyncData))]
+ public override async Task Where_DateOnly_DayOfYear(bool async)
+ {
+ await base.Where_DateOnly_DayOfYear(async);
+
+ AssertSql(
+ @"SELECT ""m"".""Id"", ""m"".""CodeName"", ""m"".""Date"", ""m"".""Duration"", ""m"".""Rating"", ""m"".""Time"", ""m"".""Timeline""
+FROM ""Missions"" AS ""m""
+WHERE CAST(strftime('%j', ""m"".""Date"") AS INTEGER) = 314");
+ }
+
+ [ConditionalTheory]
+ [MemberData(nameof(IsAsyncData))]
+ public override async Task Where_DateOnly_DayOfWeek(bool async)
+ {
+ await base.Where_DateOnly_DayOfWeek(async);
+
+ AssertSql(
+ @"SELECT ""m"".""Id"", ""m"".""CodeName"", ""m"".""Date"", ""m"".""Duration"", ""m"".""Rating"", ""m"".""Time"", ""m"".""Timeline""
+FROM ""Missions"" AS ""m""
+WHERE CAST(strftime('%w', ""m"".""Date"") AS INTEGER) = 6");
+ }
+
+ [ConditionalTheory]
+ [MemberData(nameof(IsAsyncData))]
+ public override async Task Where_DateOnly_AddYears(bool async)
+ {
+ await base.Where_DateOnly_AddYears(async);
+
+ AssertSql(
+ @"SELECT ""m"".""Id"", ""m"".""CodeName"", ""m"".""Date"", ""m"".""Duration"", ""m"".""Rating"", ""m"".""Time"", ""m"".""Timeline""
+FROM ""Missions"" AS ""m""
+WHERE date(""m"".""Date"", CAST(3 AS TEXT) || ' years') = '1993-11-10'");
+ }
+
+ [ConditionalTheory]
+ [MemberData(nameof(IsAsyncData))]
+ public override async Task Where_DateOnly_AddMonths(bool async)
+ {
+ await base.Where_DateOnly_AddMonths(async);
+
+ AssertSql(
+ @"SELECT ""m"".""Id"", ""m"".""CodeName"", ""m"".""Date"", ""m"".""Duration"", ""m"".""Rating"", ""m"".""Time"", ""m"".""Timeline""
+FROM ""Missions"" AS ""m""
+WHERE date(""m"".""Date"", CAST(3 AS TEXT) || ' months') = '1991-02-10'");
+ }
+
+ [ConditionalTheory]
+ [MemberData(nameof(IsAsyncData))]
+ public override async Task Where_DateOnly_AddDays(bool async)
+ {
+ await base.Where_DateOnly_AddDays(async);
+
+ AssertSql(
+ @"SELECT ""m"".""Id"", ""m"".""CodeName"", ""m"".""Date"", ""m"".""Duration"", ""m"".""Rating"", ""m"".""Time"", ""m"".""Timeline""
+FROM ""Missions"" AS ""m""
+WHERE date(""m"".""Date"", CAST(3 AS TEXT) || ' days') = '1990-11-13'");
+ }
+
+ [ConditionalTheory(Skip = "#18844")]
+ [MemberData(nameof(IsAsyncData))]
+ public override async Task Where_TimeOnly_Hour(bool async)
+ {
+ await base.Where_TimeOnly_Hour(async);
+
+ AssertSql("");
+ }
+
+ [ConditionalTheory(Skip = "#18844")]
+ [MemberData(nameof(IsAsyncData))]
+ public override async Task Where_TimeOnly_Minute(bool async)
+ {
+ await base.Where_TimeOnly_Minute(async);
+
+ AssertSql("");
+ }
+
+ [ConditionalTheory(Skip = "#18844")]
+ [MemberData(nameof(IsAsyncData))]
+ public override async Task Where_TimeOnly_Second(bool async)
+ {
+ await base.Where_TimeOnly_Second(async);
+
+ AssertSql("");
+ }
+
+ [ConditionalTheory(Skip = "#18844")]
+ [MemberData(nameof(IsAsyncData))]
+ public override async Task Where_TimeOnly_Millisecond(bool async)
+ {
+ await base.Where_TimeOnly_Millisecond(async);
+
+ AssertSql("");
+ }
+
+ [ConditionalTheory(Skip = "#18844")]
+ [MemberData(nameof(IsAsyncData))]
+ public override async Task Where_TimeOnly_AddHours(bool async)
+ {
+ await base.Where_TimeOnly_AddHours(async);
+
+ AssertSql("");
+ }
+
+ [ConditionalTheory(Skip = "#18844")]
+ [MemberData(nameof(IsAsyncData))]
+ public override async Task Where_TimeOnly_AddMinutes(bool async)
+ {
+ await base.Where_TimeOnly_AddMinutes(async);
+
+ AssertSql("");
+ }
+
+ [ConditionalTheory(Skip = "#18844")]
+ [MemberData(nameof(IsAsyncData))]
+ public override async Task Where_TimeOnly_Add_TimeSpan(bool async)
+ {
+ await base.Where_TimeOnly_Add_TimeSpan(async);
+
+ AssertSql("");
+ }
+
+ [ConditionalTheory(Skip = "#18844")]
+ [MemberData(nameof(IsAsyncData))]
+ public override async Task Where_TimeOnly_IsBetween(bool async)
+ {
+ await base.Where_TimeOnly_IsBetween(async);
+
+ AssertSql("");
+ }
+
+ [ConditionalTheory(Skip = "#18844")]
+ [MemberData(nameof(IsAsyncData))]
+ public override async Task Where_TimeOnly_subtract_TimeOnly(bool async)
+ {
+ await base.Where_TimeOnly_subtract_TimeOnly(async);
+
+ AssertSql("");
+ }
+
private void AssertSql(params string[] expected)
=> Fixture.TestSqlLoggerFactory.AssertBaseline(expected);
}
diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/TPTGearsOfWarQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/TPTGearsOfWarQuerySqliteTest.cs
index da6dd054028..d8d4b1f16ee 100644
--- a/test/EFCore.Sqlite.FunctionalTests/Query/TPTGearsOfWarQuerySqliteTest.cs
+++ b/test/EFCore.Sqlite.FunctionalTests/Query/TPTGearsOfWarQuerySqliteTest.cs
@@ -342,6 +342,87 @@ public override Task Array_access_on_byte_array(bool async)
return base.Array_access_on_byte_array(async);
}
+ [ConditionalTheory(Skip = "Issue#18844")]
+ [MemberData(nameof(IsAsyncData))]
+ public override async Task Where_TimeOnly_Hour(bool async)
+ {
+ await base.Where_TimeOnly_Hour(async);
+
+ AssertSql("");
+ }
+
+ [ConditionalTheory(Skip = "Issue#18844")]
+ [MemberData(nameof(IsAsyncData))]
+ public override async Task Where_TimeOnly_Minute(bool async)
+ {
+ await base.Where_TimeOnly_Minute(async);
+
+ AssertSql("");
+ }
+
+ [ConditionalTheory(Skip = "Issue#18844")]
+ [MemberData(nameof(IsAsyncData))]
+ public override async Task Where_TimeOnly_Second(bool async)
+ {
+ await base.Where_TimeOnly_Second(async);
+
+ AssertSql("");
+ }
+
+ [ConditionalTheory(Skip = "Issue#18844")]
+ [MemberData(nameof(IsAsyncData))]
+ public override async Task Where_TimeOnly_Millisecond(bool async)
+ {
+ await base.Where_TimeOnly_Millisecond(async);
+
+ AssertSql("");
+ }
+
+ [ConditionalTheory(Skip = "Issue#18844")]
+ [MemberData(nameof(IsAsyncData))]
+ public override async Task Where_TimeOnly_AddHours(bool async)
+ {
+ await base.Where_TimeOnly_AddHours(async);
+
+ AssertSql("");
+ }
+
+ [ConditionalTheory(Skip = "Issue#18844")]
+ [MemberData(nameof(IsAsyncData))]
+ public override async Task Where_TimeOnly_AddMinutes(bool async)
+ {
+ await base.Where_TimeOnly_AddMinutes(async);
+
+ AssertSql("");
+ }
+
+ [ConditionalTheory(Skip = "Issue#18844")]
+ [MemberData(nameof(IsAsyncData))]
+ public override async Task Where_TimeOnly_Add_TimeSpan(bool async)
+ {
+ await base.Where_TimeOnly_Add_TimeSpan(async);
+
+ AssertSql("");
+ }
+
+ [ConditionalTheory(Skip = "Issue#18844")]
+ [MemberData(nameof(IsAsyncData))]
+ public override async Task Where_TimeOnly_IsBetween(bool async)
+ {
+ await base.Where_TimeOnly_IsBetween(async);
+
+ AssertSql("");
+ }
+
+ [ConditionalTheory(Skip = "Issue#18844")]
+ [MemberData(nameof(IsAsyncData))]
+ public override async Task Where_TimeOnly_subtract_TimeOnly(bool async)
+ {
+ await base.Where_TimeOnly_subtract_TimeOnly(async);
+
+ AssertSql("");
+ }
+
private void AssertSql(params string[] expected)
=> Fixture.TestSqlLoggerFactory.AssertBaseline(expected);
}
diff --git a/test/EFCore.Sqlite.Tests/Storage/SqliteTypeMappingTest.cs b/test/EFCore.Sqlite.Tests/Storage/SqliteTypeMappingTest.cs
index 47f14121b2c..c850c2c94c8 100644
--- a/test/EFCore.Sqlite.Tests/Storage/SqliteTypeMappingTest.cs
+++ b/test/EFCore.Sqlite.Tests/Storage/SqliteTypeMappingTest.cs
@@ -133,6 +133,33 @@ public override void DateTime_literal_generated_correctly()
"'2015-03-12 13:36:37.371'");
}
+ [ConditionalFact]
+ public override void DateOnly_literal_generated_correctly()
+ {
+ Test_GenerateSqlLiteral_helper(
+ GetMapping(typeof(DateOnly)),
+ new DateOnly(2015, 3, 12),
+ "'2015-03-12'");
+ }
+
+ [ConditionalFact]
+ public override void TimeOnly_literal_generated_correctly()
+ {
+ Test_GenerateSqlLiteral_helper(
+ GetMapping(typeof(TimeOnly)),
+ new TimeOnly(13, 10, 15),
+ "'13:10:15'");
+ }
+
+ [ConditionalFact]
+ public override void TimeOnly_literal_generated_correctly_with_milliseconds()
+ {
+ Test_GenerateSqlLiteral_helper(
+ GetMapping(typeof(TimeOnly)),
+ new TimeOnly(13, 10, 15, 500),
+ "'13:10:15.5000000'");
+ }
+
public override void Decimal_literal_generated_correctly()
{
var typeMapping = new SqliteDecimalTypeMapping("TEXT");
diff --git a/test/Microsoft.Data.Sqlite.Tests/SqliteDataReaderTest.cs b/test/Microsoft.Data.Sqlite.Tests/SqliteDataReaderTest.cs
index 75a0325da68..7f40265523c 100644
--- a/test/Microsoft.Data.Sqlite.Tests/SqliteDataReaderTest.cs
+++ b/test/Microsoft.Data.Sqlite.Tests/SqliteDataReaderTest.cs
@@ -688,6 +688,30 @@ public void GetTimeSpan_throws_when_non_query()
public void GetDateTimeOffset_throws_when_null()
=> GetX_throws_when_null(r => ((SqliteDataReader)r).GetDateTimeOffset(0));
+ [Fact]
+ public void GetFieldValue_of_DateOnly_works()
+ => GetFieldValue_works(
+ "SELECT '2014-04-15';",
+ new DateOnly(2014, 4, 15));
+
+ [Fact]
+ public void GetFieldValue_of_DateOnly_works_with_real()
+ => GetFieldValue_works(
+ "SELECT julianday('2014-04-15');",
+ new DateOnly(2014, 4, 15));
+
+ [Fact]
+ public void GetFieldValue_of_TimeOnly_works()
+ => GetFieldValue_works(
+ "SELECT '13:10:15';",
+ new TimeOnly(13, 10, 15));
+
+ [Fact]
+ public void GetFieldValue_of_TimeOnly_works_with_milliseconds()
+ => GetFieldValue_works(
+ "SELECT '13:10:15.5';",
+ new TimeOnly(13, 10, 15, 500));
+
[Theory]
[InlineData("SELECT 1;", "INTEGER")]
[InlineData("SELECT 3.14;", "REAL")]
diff --git a/test/Microsoft.Data.Sqlite.Tests/SqliteParameterTest.cs b/test/Microsoft.Data.Sqlite.Tests/SqliteParameterTest.cs
index 6bfc38c0c39..95d48023e97 100644
--- a/test/Microsoft.Data.Sqlite.Tests/SqliteParameterTest.cs
+++ b/test/Microsoft.Data.Sqlite.Tests/SqliteParameterTest.cs
@@ -257,6 +257,26 @@ public void Bind_works_when_DateTimeOffset_with_SqliteType_Real()
2456761.9680439816,
SqliteType.Real);
+ [Fact]
+ public void Bind_works_when_DateOnly()
+ => Bind_works(new DateOnly(2014, 4, 14), "2014-04-14");
+
+ [Fact]
+ public void Bind_works_when_DateOnly_with_SqliteType_Real()
+ => Bind_works(new DateOnly(2014, 4, 14), 2456761.5, SqliteType.Real);
+
+ [Fact]
+ public void Bind_works_when_TimeOnly()
+ => Bind_works(new TimeOnly(13, 10, 15), "13:10:15");
+
+ [Fact]
+ public void Bind_works_when_TimeOnly_with_milliseconds()
+ => Bind_works(new TimeOnly(13, 10, 15, 500), "13:10:15.5000000");
+
+ [Fact]
+ public void Bind_works_when_TimeOnly_with_SqliteType_Real()
+ => Bind_works(new TimeOnly(13, 10, 15), 0.5487847222222222, SqliteType.Real);
+
[Fact]
public void Bind_works_when_DBNull()
=> Bind_works(DBNull.Value, DBNull.Value);