diff --git a/src/EFCore.Sqlite.Core/Query/Internal/SqliteMemberTranslatorProvider.cs b/src/EFCore.Sqlite.Core/Query/Internal/SqliteMemberTranslatorProvider.cs index 25adce48e5f..e914fd1ebbb 100644 --- a/src/EFCore.Sqlite.Core/Query/Internal/SqliteMemberTranslatorProvider.cs +++ b/src/EFCore.Sqlite.Core/Query/Internal/SqliteMemberTranslatorProvider.cs @@ -26,7 +26,8 @@ public SqliteMemberTranslatorProvider(RelationalMemberTranslatorProviderDependen [ new SqliteDateTimeMemberTranslator(sqlExpressionFactory), new SqliteStringLengthTranslator(sqlExpressionFactory), - new SqliteDateOnlyMemberTranslator(sqlExpressionFactory) + new SqliteDateOnlyMemberTranslator(sqlExpressionFactory), + new SqliteTimeOnlyMemberTranslator(sqlExpressionFactory) ]); } } diff --git a/src/EFCore.Sqlite.Core/Query/Internal/Translators/SqliteTimeOnlyMemberTranslator.cs b/src/EFCore.Sqlite.Core/Query/Internal/Translators/SqliteTimeOnlyMemberTranslator.cs new file mode 100644 index 00000000000..6905bd78003 --- /dev/null +++ b/src/EFCore.Sqlite.Core/Query/Internal/Translators/SqliteTimeOnlyMemberTranslator.cs @@ -0,0 +1,51 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.Query.SqlExpressions; + +// ReSharper disable once CheckNamespace +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 SqliteTimeOnlyMemberTranslator(SqliteSqlExpressionFactory sqlExpressionFactory) : IMemberTranslator +{ + /// + /// 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) + { + if (member.DeclaringType != typeof(TimeOnly) || instance is null) + { + return null; + } + + return member.Name switch + { + nameof(TimeOnly.Hour) => DatePart("%H"), + nameof(TimeOnly.Minute) => DatePart("%M"), + nameof(TimeOnly.Second) => DatePart("%S"), + + _ => null + }; + + SqlExpression DatePart(string datePart) + => sqlExpressionFactory.Convert( + sqlExpressionFactory.Strftime( + typeof(string), + datePart, + instance), + returnType); + } +} diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/Translations/Temporal/TimeOnlyTranslationsSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/Translations/Temporal/TimeOnlyTranslationsSqliteTest.cs index e784c3e03b2..eae438fecd7 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/Translations/Temporal/TimeOnlyTranslationsSqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/Translations/Temporal/TimeOnlyTranslationsSqliteTest.cs @@ -14,26 +14,38 @@ public TimeOnlyTranslationsSqliteTest(BasicTypesQuerySqliteFixture fixture, ITes public override async Task Hour() { - // TimeSpan. Issue #18844. - await AssertTranslationFailed(() => base.Hour()); + await base.Hour(); - AssertSql(); + AssertSql( + """ +SELECT "b"."Id", "b"."Bool", "b"."Byte", "b"."ByteArray", "b"."DateOnly", "b"."DateTime", "b"."DateTimeOffset", "b"."Decimal", "b"."Double", "b"."Enum", "b"."FlagsEnum", "b"."Float", "b"."Guid", "b"."Int", "b"."Long", "b"."Short", "b"."String", "b"."TimeOnly", "b"."TimeSpan" +FROM "BasicTypesEntities" AS "b" +WHERE CAST(strftime('%H', "b"."TimeOnly") AS INTEGER) = 15 +"""); } public override async Task Minute() { - // TimeSpan. Issue #18844. - await AssertTranslationFailed(() => base.Minute()); + await base.Minute(); - AssertSql(); + AssertSql( + """ +SELECT "b"."Id", "b"."Bool", "b"."Byte", "b"."ByteArray", "b"."DateOnly", "b"."DateTime", "b"."DateTimeOffset", "b"."Decimal", "b"."Double", "b"."Enum", "b"."FlagsEnum", "b"."Float", "b"."Guid", "b"."Int", "b"."Long", "b"."Short", "b"."String", "b"."TimeOnly", "b"."TimeSpan" +FROM "BasicTypesEntities" AS "b" +WHERE CAST(strftime('%M', "b"."TimeOnly") AS INTEGER) = 30 +"""); } public override async Task Second() { - // TimeSpan. Issue #18844. - await AssertTranslationFailed(() => base.Second()); + await base.Second(); - AssertSql(); + AssertSql( + """ +SELECT "b"."Id", "b"."Bool", "b"."Byte", "b"."ByteArray", "b"."DateOnly", "b"."DateTime", "b"."DateTimeOffset", "b"."Decimal", "b"."Double", "b"."Enum", "b"."FlagsEnum", "b"."Float", "b"."Guid", "b"."Int", "b"."Long", "b"."Short", "b"."String", "b"."TimeOnly", "b"."TimeSpan" +FROM "BasicTypesEntities" AS "b" +WHERE CAST(strftime('%S', "b"."TimeOnly") AS INTEGER) = 10 +"""); } public override async Task Millisecond()