未验证 提交 93f81dc0 编写于 作者: S Shay Rojansky 提交者: GitHub

Map NodaTime DateTimeZone to text (#2765)

上级 c9bc9c18
// ReSharper disable once CheckNamespace
namespace Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal;
/// <summary>
/// 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.
/// </summary>
public class DateTimeZoneMapping : RelationalTypeMapping
{
/// <summary>
/// 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.
/// </summary>
public DateTimeZoneMapping(string storeType)
: base(
new RelationalTypeMappingParameters(
new(typeof(DateTimeZone), new DateTimeZoneConverter(), new DateTimeZoneComparer()), storeType))
{
}
/// <summary>
/// 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.
/// </summary>
protected DateTimeZoneMapping(RelationalTypeMappingParameters parameters)
: base(parameters)
{
}
/// <summary>
/// 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.
/// </summary>
protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters)
=> new DateTimeZoneMapping(parameters);
/// <summary>
/// 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.
/// </summary>
public override Expression GenerateCodeLiteral(object value)
=> Expression.Call(
Expression.Property(null, typeof(DateTimeZoneProviders).GetProperty(nameof(DateTimeZoneProviders.Tzdb))!),
typeof(IDateTimeZoneProvider).GetMethod(nameof(IDateTimeZoneProvider.GetZoneOrNull), new[] { typeof(string) })!,
Expression.Constant(((DateTimeZone)value).Id));
private sealed class DateTimeZoneConverter : ValueConverter<DateTimeZone, string>
{
public DateTimeZoneConverter()
: base(
tz => tz.Id,
id => DateTimeZoneProviders.Tzdb[id])
{
}
}
private sealed class DateTimeZoneComparer : ValueComparer<DateTimeZone>
{
public DateTimeZoneComparer()
: base(
(tz1, tz2) => tz1 == null ? tz2 == null : tz2 != null && tz1.Id == tz2.Id,
tz => tz.GetHashCode())
{
}
}
}
......@@ -57,6 +57,9 @@ static NpgsqlNodaTimeTypeMappingSourcePlugin()
private readonly PeriodIntervalMapping _periodInterval = new();
private readonly DurationIntervalMapping _durationInterval = new();
// PostgreSQL has no native type for representing time zones - it just uses the IANA ID as text.
private readonly DateTimeZoneMapping _timeZone = new("text");
// Built-in ranges
private readonly NpgsqlRangeTypeMapping _timestampLocalDateTimeRange;
private readonly NpgsqlRangeTypeMapping _legacyTimestampInstantRange;
......@@ -199,6 +202,7 @@ public NpgsqlNodaTimeTypeMappingSourcePlugin(ISqlGenerationHelper sqlGenerationH
{ typeof(OffsetTime), _timetz },
{ typeof(Period), _periodInterval },
{ typeof(Duration), _durationInterval },
// See DateTimeZone below
{ typeof(NpgsqlRange<Instant>), LegacyTimestampBehavior ? _legacyTimestampInstantRange : _timestamptzInstantRange },
{ typeof(NpgsqlRange<LocalDateTime>), _timestampLocalDateTimeRange },
......@@ -290,9 +294,20 @@ public NpgsqlNodaTimeTypeMappingSourcePlugin(ISqlGenerationHelper sqlGenerationH
}
}
return clrType is not null && ClrTypeMappings.TryGetValue(clrType, out var mapping)
? mapping
: null;
if (clrType is not null)
{
if (ClrTypeMappings.TryGetValue(clrType, out var mapping))
{
return mapping;
}
if (clrType.IsAssignableTo(typeof(DateTimeZone)))
{
return _timeZone;
}
}
return null;
}
/// <summary>
......
......@@ -30,22 +30,9 @@ public class NpgsqlSqlTranslatingExpressionVisitor : RelationalSqlTranslatingExp
private static readonly ConstructorInfo DateOnlyCtor =
typeof(DateOnly).GetConstructor(new[] { typeof(int), typeof(int), typeof(int) })!;
private static readonly MethodInfo Like2MethodInfo =
typeof(DbFunctionsExtensions).GetRuntimeMethod(
nameof(DbFunctionsExtensions.Like), new[] { typeof(DbFunctions), typeof(string), typeof(string) })!;
// ReSharper disable once InconsistentNaming
private static readonly MethodInfo ILike2MethodInfo
= typeof(NpgsqlDbFunctionsExtensions).GetRuntimeMethod(
nameof(NpgsqlDbFunctionsExtensions.ILike), new[] { typeof(DbFunctions), typeof(string), typeof(string) })!;
private static readonly MethodInfo ObjectEquals
= typeof(object).GetRuntimeMethod(nameof(object.Equals), new[] { typeof(object), typeof(object) })!;
private readonly NpgsqlSqlExpressionFactory _sqlExpressionFactory;
private readonly IRelationalTypeMappingSource _typeMappingSource;
private readonly NpgsqlJsonPocoTranslator _jsonPocoTranslator;
private readonly NpgsqlLTreeTranslator _ltreeTranslator;
private readonly RelationalTypeMapping _timestampMapping;
private readonly RelationalTypeMapping _timestampTzMapping;
......@@ -66,7 +53,6 @@ public class NpgsqlSqlTranslatingExpressionVisitor : RelationalSqlTranslatingExp
{
_sqlExpressionFactory = (NpgsqlSqlExpressionFactory)dependencies.SqlExpressionFactory;
_jsonPocoTranslator = ((NpgsqlMemberTranslatorProvider)Dependencies.MemberTranslatorProvider).JsonPocoTranslator;
_ltreeTranslator = ((NpgsqlMethodCallTranslatorProvider)Dependencies.MethodCallTranslatorProvider).LTreeTranslator;
_typeMappingSource = dependencies.TypeMappingSource;
_timestampMapping = _typeMappingSource.FindMapping("timestamp without time zone")!;
_timestampTzMapping = _typeMappingSource.FindMapping("timestamp with time zone")!;
......
......@@ -1540,7 +1540,7 @@ public async Task Instance_InUtc(bool async)
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public async Task Instance_InZone_LocalDateTime(bool async)
public async Task Instance_InZone_constant_LocalDateTime(bool async)
{
await AssertQuery(
async,
......@@ -1558,7 +1558,7 @@ public async Task Instance_InZone_LocalDateTime(bool async)
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public async Task Instance_InZone_Date(bool async)
public async Task Instance_InZone_constant_Date(bool async)
{
await AssertQuery(
async,
......@@ -1574,6 +1574,28 @@ public async Task Instance_InZone_Date(bool async)
""");
}
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public async Task Instance_InZone_parameter_LocalDateTime(bool async)
{
var timeZone = DateTimeZoneProviders.Tzdb["Europe/Berlin"];
await AssertQuery(
async,
ss => ss.Set<NodaTimeTypes>().Where(t => t.Instant.InZone(timeZone).LocalDateTime
== new LocalDateTime(2018, 4, 20, 12, 31, 33, 666)),
entryCount: 1);
AssertSql(
"""
@__timeZone_0='Europe/Berlin'
SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime"
FROM "NodaTimeTypes" AS n
WHERE n."Instant" AT TIME ZONE @__timeZone_0 = TIMESTAMP '2018-04-20T12:31:33.666'
""");
}
[ConditionalFact]
public async Task Instance_InZone_without_LocalDateTime_fails()
{
......
......@@ -589,6 +589,33 @@ public void GenerateCodeLiteral_returns_Duration_literal()
#endregion interval
#region DateTimeZone
[Fact]
public void DateTimeZone_is_properly_mapped()
{
var mapping = GetMapping(typeof(DateTimeZone));
Assert.Same(typeof(DateTimeZone), mapping.ClrType);
Assert.Equal("text", mapping.StoreType);
}
[Fact]
public void GenerateSqlLiteral_returns_DateTimeZone_literal()
{
var mapping = GetMapping(typeof(DateTimeZone));
Assert.Equal("Europe/Berlin", mapping.GenerateSqlLiteral(DateTimeZoneProviders.Tzdb["Europe/Berlin"]));
}
[Fact]
public void GenerateCodeLiteral_returns_DateTimezone_literal()
=> Assert.Equal(
"""NodaTime.DateTimeZoneProviders.Tzdb.GetZoneOrNull("Europe/Berlin")""",
CodeLiteral(DateTimeZoneProviders.Tzdb["Europe/Berlin"]));
#endregion
#region Support
private static readonly NpgsqlTypeMappingSource Mapper = new(
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册