From an earlier post, you already know that I'm a fan of using NodaTime (see post Handling Date/Time with precision using NodaTime).

However, there was a workaround required if you wanted to use LocalDate (or any other NodaTime type) in your Domain models as EF Core wasn't able to map LocalDate to a known database type.

For example, on our team worked around this by using a string to store a LocalDate as a ISO.

[UsedImplicitly(ImplicitUseTargetFlags.WithMembers)]
public class Customer : DomainEntity
{
    [UsedImplicitly]
    private Customer() 
    {
    }

    public Customer(string name, string email, LocalDate? dateOfBirth)
    {
        Name = name;
        Email = email;
        DateOfBirthIso = dateOfBirth.Value.ToIsoString();
    }

    [MaxLength(255)]
    public string Name { get; private set; }

    [MaxLength(320)]
    public string Email { get; private set; }

    [MaxLength(10)]
    public string DateOfBirthIso { get; private set; }
    public LocalDate? DateOfBirth => DateOfBirthIso?.ParseAsLocalDate();
}

Not ideal, but it was workable. Instead of having DateOfBirthIso as a public property, you could have also used shadow property.

Notes:

  • The [MaxLength(10)] allows the DateOfBirthIso field to be indexed.
  • [UsedImplicitly(ImplicitUseTargetFlags.WithMembers)] prevents R# stripping out unused private set from the model which will break your implementation.
  • This is a contrieved example, as there isn't a good reason to capture a date of birth for a customer.

EF Core 2.1

GOOD NEWS!

Starting with EF Core 2.1 value conversions can be applied to transform the values obtained from columns before they are applied to properties, and vice versa Value Conversions.

This means that we can persist enums as strings in the database (if you so wish) and in our example above convert NodaTime values.

Modify the above to the following:

[UsedImplicitly(ImplicitUseTargetFlags.WithMembers)]
public class Customer : DomainEntity
{
-    [MaxLength(10)]
-    public string DateOfBirthIso { get; private set; }
-    public LocalDate? DateOfBirth => DateOfBirthIso?.ParseAsLocalDate();
+    public LocalDate? DateOfBirth { get; private set; }
}

Then in the EF Core context and then run your migrations.

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);

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

    modelBuilder.Entity<Customer>()
        .Property(e => e.DateOfBirth)
        .HasConversion(localDateConverter);
}

At the moment there it is not possible to configure this for all LocalDate properties. I haven't had much time to explore ways to improve the usage, but this already a great win already so I'm happy :).