From 83dc4e29dd1276775fdff5a80fdc6f72418435bc Mon Sep 17 00:00:00 2001 From: Laurents Meyer Date: Mon, 4 Mar 2024 16:21:07 +0100 Subject: [PATCH] Add `DateTimeOffset` member translations for `.DateTime`, `.UtcDateTime` and `.LocalDateTime`. --- .../Internal/MySqlDateTimeMemberTranslator.cs | 22 ++++++++++++ .../Query/GearsOfWarQueryMySqlTest.MySql.cs | 34 +++++++++++++++++++ .../Query/MySqlTimeZoneTest.cs | 25 ++++++++++++++ 3 files changed, 81 insertions(+) diff --git a/src/EFCore.MySql/Query/Internal/MySqlDateTimeMemberTranslator.cs b/src/EFCore.MySql/Query/Internal/MySqlDateTimeMemberTranslator.cs index ececfd693..b8aedab82 100644 --- a/src/EFCore.MySql/Query/Internal/MySqlDateTimeMemberTranslator.cs +++ b/src/EFCore.MySql/Query/Internal/MySqlDateTimeMemberTranslator.cs @@ -8,6 +8,7 @@ using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Query; using Microsoft.EntityFrameworkCore.Query.SqlExpressions; +using Pomelo.EntityFrameworkCore.MySql.Utilities; namespace Pomelo.EntityFrameworkCore.MySql.Query.Internal { @@ -141,6 +142,27 @@ public virtual SqlExpression Translate( } } + if (declaringType == typeof(DateTimeOffset)) + { + switch (member.Name) + { + case nameof(DateTimeOffset.DateTime): + case nameof(DateTimeOffset.UtcDateTime): + // We represent `DateTimeOffset` values as UTC datetime values in the database. Therefore, `DateTimeOffset`, + // `DateTimeOffset.DateTime` and `DateTimeOffset.UtcDateTime` are all the same. + return _sqlExpressionFactory.Convert(instance, typeof(DateTime)); + + case nameof(DateTimeOffset.LocalDateTime): + return _sqlExpressionFactory.NullableFunction( + "CONVERT_TZ", + [instance, _sqlExpressionFactory.Constant("+00:00"), _sqlExpressionFactory.Fragment("@@session.time_zone")], + typeof(DateTime), + null, + false, + Statics.GetTrueValues(3)); + } + } + return null; } } diff --git a/test/EFCore.MySql.FunctionalTests/Query/GearsOfWarQueryMySqlTest.MySql.cs b/test/EFCore.MySql.FunctionalTests/Query/GearsOfWarQueryMySqlTest.MySql.cs index 3214a3075..7a537a2cc 100644 --- a/test/EFCore.MySql.FunctionalTests/Query/GearsOfWarQueryMySqlTest.MySql.cs +++ b/test/EFCore.MySql.FunctionalTests/Query/GearsOfWarQueryMySqlTest.MySql.cs @@ -196,5 +196,39 @@ await AssertQuery( WHERE `w`.`IsAutomatic` = TRUE"), keys); // Breaking change in 5.0 due to bool expression optimization in `SqlNullabilityProcessor`. // Was "`w`.`IsAutomatic` <> FALSE" before. } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual async Task DateTimeOffset_DateTime(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(e => e.Timeline == e.Timeline.DateTime), + ss => ss.Set().Where(e => true)); + + AssertSql( + """ +SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Date`, `m`.`Duration`, `m`.`Rating`, `m`.`Time`, `m`.`Timeline` +FROM `Missions` AS `m` +WHERE `m`.`Timeline` = `m`.`Timeline` +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual async Task DateTimeOffset_UtcDateTime(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(e => e.Timeline == e.Timeline.UtcDateTime), + ss => ss.Set().Where(e => true)); + + AssertSql( + """ +SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Date`, `m`.`Duration`, `m`.`Rating`, `m`.`Time`, `m`.`Timeline` +FROM `Missions` AS `m` +WHERE `m`.`Timeline` = `m`.`Timeline` +"""); + } } } diff --git a/test/EFCore.MySql.Tests/Query/MySqlTimeZoneTest.cs b/test/EFCore.MySql.Tests/Query/MySqlTimeZoneTest.cs index 13407882d..acc81ad52 100644 --- a/test/EFCore.MySql.Tests/Query/MySqlTimeZoneTest.cs +++ b/test/EFCore.MySql.Tests/Query/MySqlTimeZoneTest.cs @@ -52,6 +52,30 @@ LIMIT 2 Fixture.Sql); } + [ConditionalFact] + public void DateTimeOffset_LocalDateTime() + { + using var context = Fixture.CreateContext(); + SetSessionTimeZone(context); + Fixture.ClearSql(); + + var metalContainer = context.Set() + .Where(c => c.DeliveredDateTimeOffset.LocalDateTime == MySqlTimeZoneFixture.OriginalDateTimeUtc.AddHours(MySqlTimeZoneFixture.SessionOffset)) + .Select(c => new { c.DeliveredDateTimeOffset.LocalDateTime }) + .Single(); + + Assert.Equal(MySqlTimeZoneFixture.OriginalDateTimeUtc.AddHours(MySqlTimeZoneFixture.SessionOffset), metalContainer.LocalDateTime); + + Assert.Equal( + """ +SELECT CONVERT_TZ(`c`.`DeliveredDateTimeOffset`, '+00:00', @@session.time_zone) AS `LocalDateTime` +FROM `Container` AS `c` +WHERE CONVERT_TZ(`c`.`DeliveredDateTimeOffset`, '+00:00', @@session.time_zone) = TIMESTAMP '2023-12-31 15:00:00' +LIMIT 2 +""", + Fixture.Sql); + } + private static void SetSessionTimeZone(MySqlTimeZoneFixture.MySqlTimeZoneContext context) { context.Database.OpenConnection(); @@ -63,6 +87,7 @@ private static void SetSessionTimeZone(MySqlTimeZoneFixture.MySqlTimeZoneContext public class MySqlTimeZoneFixture : MySqlTestFixtureBase { + public const int SessionOffset = -8; // UTC-8 public const int OriginalOffset = 2; // UTC+2 public static readonly DateTime OriginalDateTimeUtc = new DateTime(2023, 12, 31, 23, 0, 0); public static readonly DateTime OriginalDateTime = OriginalDateTimeUtc.AddHours(OriginalOffset);