Skip to main content

NodaTime Support

This library supports NodaTime time-column types for both hypertables and continuous aggregates The fluent API methods IsHypertable and IsContinuousAggregate, as well as the [Hypertable] data annotation, accept any .NET time-column type. Correctness is enforced at model finalization by validating the resolved PostgreSQL store type of the time column rather than the .NET type, so a NodaTime type is valid precisely when its Npgsql mapping resolves to a supported TimescaleDB time dimension.


Setup

The core library takes no dependency on NodaTime. NodaTime support is opt-in and provided by the official Npgsql plugin.

Install the NodaTime Plugin Package

Add the Npgsql NodaTime plugin to the project alongside the TimescaleDB packages:

dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL.NodaTime

Enable the Plugin

Enable the NodaTime plugin when configuring the PostgreSQL provider, then chain .UseTimescaleDb():

string? connectionString = builder.Configuration.GetConnectionString("Timescale");

builder.Services.AddDbContext<TimescaleContext>(options =>
options.UseNpgsql(connectionString, o => o.UseNodaTime()).UseTimescaleDb());

Once the plugin is enabled, NodaTime types such as Instant and LocalDateTime map to PostgreSQL time types and can be used as hypertable or continuous-aggregate time columns.


Using the Fluent API

A NodaTime time column requires no special configuration. Overload resolution selects the generic IsHypertable overload automatically, so no explicit type arguments are needed; builder.IsHypertable(x => x.RecordedAt) works directly when RecordedAt is a NodaTime Instant.

using NodaTime;

public class SensorMeasurement
{
public Guid Id { get; set; }
public Instant RecordedAt { get; set; }
public string SensorId { get; set; } = string.Empty;
public double Temperature { get; set; }
public double Humidity { get; set; }
}
using CmdScale.EntityFrameworkCore.TimescaleDB.Configuration.Hypertable;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;

public class SensorMeasurementConfiguration : IEntityTypeConfiguration<SensorMeasurement>
{
public void Configure(EntityTypeBuilder<SensorMeasurement> builder)
{
builder.ToTable("sensor_measurements");
builder.HasKey(x => new { x.Id, x.RecordedAt });

builder.IsHypertable(x => x.RecordedAt)
.WithChunkTimeInterval("1 day")
.EnableCompression()
.WithCompressionSegmentBy(x => x.SensorId)
.WithCompressionOrderBy(s => [s.ByDescending(x => x.RecordedAt)]);
}
}

The same applies to continuous aggregates: a NodaTime source time column works with IsContinuousAggregate without explicit type arguments.


Using Data Annotations

The [Hypertable] attribute names the time column and works with any time column, including NodaTime types. The example below uses a NodaTime LocalDateTime

using CmdScale.EntityFrameworkCore.TimescaleDB.Configuration.Hypertable;
using Microsoft.EntityFrameworkCore;
using NodaTime;

[Hypertable(nameof(ObservedAt), ChunkTimeInterval = "1 day", EnableCompression = true, CompressionSegmentBy = new[] { "Station" }, CompressionOrderBy = new[] { "ObservedAt DESC" })]
[PrimaryKey(nameof(Id), nameof(ObservedAt))]
public class EnvironmentReading
{
public Guid Id { get; set; }
public LocalDateTime ObservedAt { get; set; }
public string Station { get; set; } = string.Empty;
public double Pressure { get; set; }
public double WindSpeed { get; set; }
}

Supported and Unsupported NodaTime Types

A NodaTime type is supported when its Npgsql store mapping resolves to a valid TimescaleDB time dimension. The supported PostgreSQL time-dimension store types are timestamp without time zone / timestamp, timestamp with time zone / timestamptz, date, smallint / int2, integer / int / int4, and bigint / int8.

Supported

NodaTime TypePostgreSQL Store Type
Instanttimestamp with time zone
LocalDateTimetimestamp without time zone
LocalDatedate
ZonedDateTimetimestamp with time zone
OffsetDateTimetimestamp with time zone

Unsupported

The following NodaTime types do not map to a valid time dimension. Using one as a time column causes model building to throw:

NodaTime TypePostgreSQL Store Type
LocalTimetime without time zone
OffsetTimetime with time zone
Durationinterval
Periodinterval
Intervaltstzrange
DateIntervaldaterange

⚠️ Note: Validation is performed against the resolved PostgreSQL store type of the time column at model finalization, not against the .NET type. A NodaTime type is valid precisely because its Npgsql mapping resolves to a supported store type. When the resolved store type is not a valid TimescaleDB time dimension, model building throws an InvalidOperationException.