'EF Core NodaTime field - could not be mapped

I'm using EF Core with <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="5.0.2" />

I've just added a NodaTime.LocalDate field to my entity which uses a package called NodaTime:

<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL.NodaTime" Version="5.0.2" />

The field:

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Nest;

namespace Vepo.Domain
{
    [ElasticsearchType(RelationName = "eventitem", IdProperty = "Id")]
    public class EventItem : VeganItem<EventItemEstablishment>
    {
        [MaxLength(10)]
        public List<NpgsqlRange<LocalDateTime>> Dates { get; set; }
    }
}

and I now get this error:

InvalidOperationException: The property ‘EventItem.Dates’ could not be mapped, because it is of type List<NpgsqlRange<LocalDateTime>> which is not a supported primitive type or a valid entity type. Either explicitly map this property, or ignore it using the ‘[NotMapped]’ attribute or by using ‘EntityTypeBuilder.Ignore’ in ‘OnModelCreating’.

So Ef Core cannot map List<NpgsqlRange>.

This is a demo of how to let EF Core map LocalDateTime by using a value converter:

base.OnModelCreating(modelBuilder);
var localDateConverter = 
    new ValueConverter<LocalDate, DateTime>(v =>  
        v.ToDateTimeUnspecified(), 
        v => LocalDate.FromDateTime(v));

modelBuilder.Entity<Event>()
    .Property(e => e.Date)
    .HasConversion(localDateConverter);

But this does not work for me because my field is not LocalDateTime, it is List<NpgsqlRange<LocalDateTime>>

I am struggling to create the value converter correctly. Any help appreciated.

FYI My front end is sending a list of custom objects to the back end: [{DateTime startDate, DateTime endDate}]

IMPORTANT: Shay Rojansky's version is working for me, I needed to change my Startup.cs code from this:

public void ConfigureServices(IServiceCollection services)
{
    services
    .AddDbContext<VepoContext>(opt => {
        opt
        .UseNpgsql(
            Configuration
            .GetConnectionString("DefaultConnection"))
            .EnableSensitiveDataLogging()
            .EnableDetailedErrors()
            .LogTo(Console.WriteLine);
    });
    
    NpgsqlConnection.GlobalTypeMapper.UseNodaTime();

To this (basically to his - but my initialising code was using ConfigureServices, not OnConfiguring so I thought I will post his solution with the ConfigureServices syntax here for people who's code is already using ConfigureServices:

    services
    .AddDbContext<VepoContext>(opt => {
        opt
        .UseNpgsql(
            Configuration
            .GetConnectionString("DefaultConnection"), 
                o => o.UseNodaTime()
            )
            .EnableSensitiveDataLogging()
            .EnableDetailedErrors()
            .LogTo(Console.WriteLine);
    });


Solution 1:[1]

The above works for me as-is using 5.0.10, without a value converter:

await using var ctx = new BlogContext();
await ctx.Database.EnsureDeletedAsync();
await ctx.Database.EnsureCreatedAsync();

public class BlogContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        => optionsBuilder
            .UseNpgsql(@"Host=localhost;Username=test;Password=test", o => o.UseNodaTime())
            .LogTo(Console.WriteLine, LogLevel.Information)
            .EnableSensitiveDataLogging();

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
    }
}

public class Blog
{
    public int Id { get; set; }
    public List<NpgsqlRange<LocalDateTime>> Dates { get; set; }
}

The following table gets created:

CREATE TABLE "Blogs" (
    "Id" integer GENERATED BY DEFAULT AS IDENTITY,
    "Dates" tsrange[] NULL,
    CONSTRAINT "PK_Blogs" PRIMARY KEY ("Id")
);

You may be interested in the new multirange type in the upcoming PostgreSQL 14 release. Support for it has already been added to version 6.0 of the EF Core provider (a release candidate is already out for both PG14 and EF Core 6.0.0), so you may want to give them a try.

Solution 2:[2]

I got this 'not a supported primitive type' error because I was following the wrong documentation for my situation. I started at https://www.npgsql.org/doc/types/nodatime.html which uses the NuGet package Npgsql.NodaTime and applies the type mapping with NpgsqlConnection.GlobalTypeMapper.UseNodaTime();

I needed to instead follow the documentation for EF Core at https://www.npgsql.org/efcore/mapping/nodatime.html which relies on the NuGet package Npgsql.EntityFrameworkCore.PostgreSQL.NodaTime and applies the type mapping via UseNodaTime() on NpgsqlDbContextOptionsBuilder

builder.UseNpgsql("Host=localhost;Database=test;Username=npgsql_tests;Password=npgsql_tests",
        o => o.UseNodaTime());

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Shay Rojansky
Solution 2 rrey