-
Notifications
You must be signed in to change notification settings - Fork 223
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Closes #333
- Loading branch information
Showing
11 changed files
with
1,028 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
# Date/Time Mapping with NodaTime | ||
|
||
> [!NOTE] | ||
> This feature is only available in Npgsql 4.0, which is currently in preview. | ||
By default, [the PostgreSQL date/time types](https://www.postgresql.org/docs/current/static/datatype-datetime.html) are mapped to the built-in .NET types (`DateTime`, `TimeSpan`). Unfortunately, these built-in types (`DateTime`, `DateTimeOffset`) are flawed in many ways; regardless of PostgreSQL or databases. The [NodaTime library](http://nodatime.org/) was created to solve many of these problems, and if your application handles dates and times in anything but the most basic way, you should seriously consider using NodaTime. To learn more [read this blog post by Jon Skeet](http://blog.nodatime.org/2011/08/what-wrong-with-datetime-anyway.html). | ||
|
||
For PostgreSQL specifically, the NodaTime types map more naturally to the database types - everything is simpler and works in a more predictable way. | ||
|
||
> [!NOTE] | ||
> This plugin, which works at the Entity Framework Core level, is distinct from [the Npgsql ADO plugin for NodaTime](http://www.npgsql.org/doc/types/nodatime.html); the EF Core plugin references and relies on the ADO plugin. | ||
# Setup | ||
|
||
To set up the NodaTime plugin, add the [Npgsql.NodaTime nuget](https://www.nuget.org/packages/Npgsql.NodaTime) to your project. Then, make the following modification to your `UseNpgsql()` line: | ||
|
||
```c# | ||
protected override void OnConfiguring(DbContextOptionsBuilder builder) | ||
{ | ||
builder.UseNpgsql("Host=localhost;Database=test;Username=npgsql_tests;Password=npgsql_tests", | ||
o => o.UseNodaTime()); | ||
} | ||
``` | ||
|
||
This will set up all the necessary mappings and operation translators. You can now use NodaTime types as regular properties in your entities, and even perform some operations: | ||
|
||
```c# | ||
public class Post | ||
{ | ||
public int Id { get; set; } | ||
public string Name { get; set; } | ||
public Instant CreationTime { get; set; } | ||
} | ||
|
||
var recentPosts = context.Posts.Where(p => p.CreationTime > someInstant); | ||
``` | ||
|
||
## Member Translation | ||
|
||
Currently, the EF Core provider knows how to translate the most date/time component members of NodaTime's `LocalDateTime`, `LocalDate`, `LocalTime` and `Period`. In other words, the following query will be translated to SQL and evaluated server-side: | ||
|
||
```c# | ||
// Get all events which occurred on a Monday | ||
var mondayEvents = context.Events.Where(p => p.SomeDate.DayOfWeek == DayOfWeek.Monday); | ||
|
||
// Get all events which occurred before the year 2000 | ||
var oldEvents = context.Events.Where(p => p.SomeDate.Year < 2000); | ||
``` | ||
|
||
Note that the plugin is far from covering all translations. If a translation you need is missing, please open an issue to request for it. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
<PropertyGroup> | ||
<VersionPrefix>2.1.0-rtm</VersionPrefix> | ||
<TargetFrameworks>netstandard2.0</TargetFrameworks> | ||
<AssemblyName>Npgsql.EntityFrameworkCore.PostgreSQL.NodaTime</AssemblyName> | ||
<RootNamespace>Npgsql.EntityFrameworkCore.PostgreSQL.NodaTime</RootNamespace> | ||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors> | ||
<AssemblyOriginatorKeyFile>../../Npgsql.snk</AssemblyOriginatorKeyFile> | ||
<SignAssembly>true</SignAssembly> | ||
<PublicSign Condition=" '$(OS)' != 'Windows_NT' ">true</PublicSign> | ||
<Description>NodaTime support plugin for PostgreSQL/Npgsql Entity Framework Core provider.</Description> | ||
<Authors>Shay Rojansky</Authors> | ||
<Company>Npgsql</Company> | ||
<PackageTags>npgsql;postgresql;postgres;Entity Framework Core;entity-framework-core;ef;efcore;orm;sql;nodatime</PackageTags> | ||
<Copyright>Copyright 2017 © The Npgsql Development Team</Copyright> | ||
<PackageIconUrl>http://www.npgsql.org/img/postgresql.gif</PackageIconUrl> | ||
<PackageLicenseUrl>https://raw.githubusercontent.com/npgsql/Npgsql.EntityFrameworkCore.PostgreSQL/dev/LICENSE.txt</PackageLicenseUrl> | ||
<RepositoryType>git</RepositoryType> | ||
<RepositoryUrl>git://github.com/npgsql/Npgsql.EntityFrameworkCore.PostgreSQL</RepositoryUrl> | ||
<LangVersion>latest</LangVersion> | ||
</PropertyGroup> | ||
<ItemGroup> | ||
<Compile Include="..\Shared\*.cs" /> | ||
</ItemGroup> | ||
<ItemGroup> | ||
<ProjectReference Include="..\EFCore.PG\EFCore.PG.csproj" /> | ||
</ItemGroup> | ||
<ItemGroup> | ||
<PackageReference Include="Npgsql" Version="4.0.0-ci.*" /> | ||
<PackageReference Include="Npgsql.NodaTime" Version="1.0.0-ci.*" /> | ||
</ItemGroup> | ||
</Project> |
25 changes: 25 additions & 0 deletions
25
src/EFCore.PG.NodaTime/NodaTimeDbContextOptionsExtensions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
using JetBrains.Annotations; | ||
using Npgsql; | ||
using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure; | ||
using Npgsql.EntityFrameworkCore.PostgreSQL.NodaTime; | ||
using Npgsql.EntityFrameworkCore.PostgreSQL.Utilities; | ||
|
||
// ReSharper disable once CheckNamespace | ||
namespace Microsoft.EntityFrameworkCore | ||
{ | ||
public static class NodaTimeDbContextOptionsExtensions | ||
{ | ||
public static NpgsqlDbContextOptionsBuilder UseNodaTime( | ||
[NotNull] this NpgsqlDbContextOptionsBuilder optionsBuilder) | ||
{ | ||
Check.NotNull(optionsBuilder, nameof(optionsBuilder)); | ||
|
||
// TODO: Global-only setup at the ADO.NET level for now, optionally allow per-connection? | ||
NpgsqlConnection.GlobalTypeMapper.UseNodatime(); | ||
|
||
optionsBuilder.UsePlugin(new NodaTimePlugin()); | ||
|
||
return optionsBuilder; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,194 @@ | ||
using System; | ||
using Microsoft.EntityFrameworkCore.Storage; | ||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion; | ||
using NodaTime; | ||
using NodaTime.Text; | ||
using Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Mapping; | ||
using NpgsqlTypes; | ||
|
||
namespace Npgsql.EntityFrameworkCore.PostgreSQL.NodaTime | ||
{ | ||
#region timestamp | ||
|
||
public class TimestampInstantMapping : NpgsqlTypeMapping | ||
{ | ||
public TimestampInstantMapping() : base("timestamp", typeof(Instant), NpgsqlDbType.Timestamp) {} | ||
|
||
protected TimestampInstantMapping(RelationalTypeMappingParameters parameters) | ||
: base(parameters, NpgsqlDbType.Timestamp) {} | ||
|
||
public override RelationalTypeMapping Clone(string storeType, int? size) | ||
=> new TimestampInstantMapping(Parameters.WithStoreTypeAndSize(storeType, size)); | ||
|
||
public override CoreTypeMapping Clone(ValueConverter converter) | ||
=> new TimestampInstantMapping(Parameters.WithComposedConverter(converter)); | ||
|
||
protected override string GenerateNonNullSqlLiteral(object value) | ||
=> $"TIMESTAMP '{InstantPattern.ExtendedIso.Format((Instant)value)}'"; | ||
} | ||
|
||
public class TimestampLocalDateTimeMapping : NpgsqlTypeMapping | ||
{ | ||
public TimestampLocalDateTimeMapping() : base("timestamp", typeof(LocalDateTime), NpgsqlDbType.Timestamp) {} | ||
|
||
protected TimestampLocalDateTimeMapping(RelationalTypeMappingParameters parameters) | ||
: base(parameters, NpgsqlDbType.Timestamp) {} | ||
|
||
public override RelationalTypeMapping Clone(string storeType, int? size) | ||
=> new TimestampLocalDateTimeMapping(Parameters.WithStoreTypeAndSize(storeType, size)); | ||
|
||
public override CoreTypeMapping Clone(ValueConverter converter) | ||
=> new TimestampLocalDateTimeMapping(Parameters.WithComposedConverter(converter)); | ||
|
||
protected override string GenerateNonNullSqlLiteral(object value) | ||
=> $"TIMESTAMP '{LocalDateTimePattern.ExtendedIso.Format((LocalDateTime)value)}'"; | ||
} | ||
|
||
#endregion timestamp | ||
|
||
#region timestamptz | ||
|
||
public class TimestampTzInstantMapping : NpgsqlTypeMapping | ||
{ | ||
public TimestampTzInstantMapping() : base("timestamp with time zone", typeof(Instant), NpgsqlDbType.TimestampTz) {} | ||
|
||
protected TimestampTzInstantMapping(RelationalTypeMappingParameters parameters) | ||
: base(parameters, NpgsqlDbType.TimestampTz) {} | ||
|
||
public override RelationalTypeMapping Clone(string storeType, int? size) | ||
=> new TimestampTzInstantMapping(Parameters.WithStoreTypeAndSize(storeType, size)); | ||
|
||
public override CoreTypeMapping Clone(ValueConverter converter) | ||
=> new TimestampTzInstantMapping(Parameters.WithComposedConverter(converter)); | ||
|
||
protected override string GenerateNonNullSqlLiteral(object value) | ||
=> $"TIMESTAMPTZ '{InstantPattern.ExtendedIso.Format((Instant)value)}'"; | ||
} | ||
|
||
public class TimestampTzOffsetDateTimeMapping : NpgsqlTypeMapping | ||
{ | ||
public TimestampTzOffsetDateTimeMapping() : base("timestamp with time zone", typeof(OffsetDateTime), NpgsqlDbType.TimestampTz) {} | ||
|
||
protected TimestampTzOffsetDateTimeMapping(RelationalTypeMappingParameters parameters) | ||
: base(parameters, NpgsqlDbType.TimestampTz) {} | ||
|
||
public override RelationalTypeMapping Clone(string storeType, int? size) | ||
=> new TimestampTzOffsetDateTimeMapping(Parameters.WithStoreTypeAndSize(storeType, size)); | ||
|
||
public override CoreTypeMapping Clone(ValueConverter converter) | ||
=> new TimestampTzOffsetDateTimeMapping(Parameters.WithComposedConverter(converter)); | ||
|
||
protected override string GenerateNonNullSqlLiteral(object value) | ||
=> $"TIMESTAMPTZ '{OffsetDateTimePattern.ExtendedIso.Format((OffsetDateTime)value)}'"; | ||
} | ||
|
||
public class TimestampTzZonedDateTimeMapping : NpgsqlTypeMapping | ||
{ | ||
static readonly ZonedDateTimePattern Pattern = | ||
ZonedDateTimePattern.CreateWithInvariantCulture("uuuu'-'MM'-'dd'T'HH':'mm':'ss;FFFFFFo<G>", | ||
DateTimeZoneProviders.Tzdb); | ||
|
||
public TimestampTzZonedDateTimeMapping() : base("timestamp with time zone", typeof(ZonedDateTime), NpgsqlDbType.TimestampTz) {} | ||
|
||
protected TimestampTzZonedDateTimeMapping(RelationalTypeMappingParameters parameters) | ||
: base(parameters, NpgsqlDbType.TimestampTz) {} | ||
|
||
public override RelationalTypeMapping Clone(string storeType, int? size) | ||
=> new TimestampTzZonedDateTimeMapping(Parameters.WithStoreTypeAndSize(storeType, size)); | ||
|
||
public override CoreTypeMapping Clone(ValueConverter converter) | ||
=> new TimestampTzZonedDateTimeMapping(Parameters.WithComposedConverter(converter)); | ||
|
||
protected override string GenerateNonNullSqlLiteral(object value) | ||
=> $"TIMESTAMPTZ '{Pattern.Format((ZonedDateTime)value)}'"; | ||
} | ||
|
||
#endregion timestamptz | ||
|
||
#region date | ||
|
||
public class DateMapping : NpgsqlTypeMapping | ||
{ | ||
public DateMapping() : base("date", typeof(LocalDate), NpgsqlDbType.Date) {} | ||
|
||
protected DateMapping(RelationalTypeMappingParameters parameters) | ||
: base(parameters, NpgsqlDbType.Date) {} | ||
|
||
public override RelationalTypeMapping Clone(string storeType, int? size) | ||
=> new DateMapping(Parameters.WithStoreTypeAndSize(storeType, size)); | ||
|
||
public override CoreTypeMapping Clone(ValueConverter converter) | ||
=> new DateMapping(Parameters.WithComposedConverter(converter)); | ||
|
||
protected override string GenerateNonNullSqlLiteral(object value) | ||
=> $"DATE '{LocalDatePattern.Iso.Format((LocalDate)value)}'"; | ||
} | ||
|
||
#endregion date | ||
|
||
#region time | ||
|
||
public class TimeMapping : NpgsqlTypeMapping | ||
{ | ||
public TimeMapping() : base("time", typeof(LocalTime), NpgsqlDbType.Time) {} | ||
|
||
protected TimeMapping(RelationalTypeMappingParameters parameters) | ||
: base(parameters, NpgsqlDbType.Time) {} | ||
|
||
public override RelationalTypeMapping Clone(string storeType, int? size) | ||
=> new TimeMapping(Parameters.WithStoreTypeAndSize(storeType, size)); | ||
|
||
public override CoreTypeMapping Clone(ValueConverter converter) | ||
=> new TimeMapping(Parameters.WithComposedConverter(converter)); | ||
|
||
protected override string GenerateNonNullSqlLiteral(object value) | ||
=> $"TIME '{LocalTimePattern.ExtendedIso.Format((LocalTime)value)}'"; | ||
} | ||
|
||
#endregion time | ||
|
||
#region timetz | ||
|
||
public class TimeTzMapping : NpgsqlTypeMapping | ||
{ | ||
static readonly OffsetTimePattern Pattern = | ||
OffsetTimePattern.CreateWithInvariantCulture("HH':'mm':'ss;FFFFFFo<G>"); | ||
|
||
public TimeTzMapping() : base("time with time zone", typeof(OffsetTime), NpgsqlDbType.TimeTz) {} | ||
|
||
protected TimeTzMapping(RelationalTypeMappingParameters parameters) | ||
: base(parameters, NpgsqlDbType.TimeTz) {} | ||
|
||
public override RelationalTypeMapping Clone(string storeType, int? size) | ||
=> new TimeTzMapping(Parameters.WithStoreTypeAndSize(storeType, size)); | ||
|
||
public override CoreTypeMapping Clone(ValueConverter converter) | ||
=> new TimeTzMapping(Parameters.WithComposedConverter(converter)); | ||
|
||
protected override string GenerateNonNullSqlLiteral(object value) | ||
=> $"TIMETZ '{Pattern.Format((OffsetTime)value)}'"; | ||
} | ||
|
||
#endregion timetz | ||
|
||
#region interval | ||
|
||
public class IntervalMapping : NpgsqlTypeMapping | ||
{ | ||
public IntervalMapping() : base("interval", typeof(Period), NpgsqlDbType.Interval) {} | ||
|
||
protected IntervalMapping(RelationalTypeMappingParameters parameters) | ||
: base(parameters, NpgsqlDbType.Interval) {} | ||
|
||
public override RelationalTypeMapping Clone(string storeType, int? size) | ||
=> new IntervalMapping(Parameters.WithStoreTypeAndSize(storeType, size)); | ||
|
||
public override CoreTypeMapping Clone(ValueConverter converter) | ||
=> new IntervalMapping(Parameters.WithComposedConverter(converter)); | ||
|
||
protected override string GenerateNonNullSqlLiteral(object value) | ||
=> $"INTERVAL '{PeriodPattern.NormalizingIso.Format((Period)value)}'"; | ||
} | ||
|
||
#endregion interval | ||
} |
Oops, something went wrong.