34

I really want to be able to use NodaTime in my Entity Framework Code First database projects but haven't found a "clean" way to do it. What I really want to do is this:

public class Photoshoot
{
    public Guid PhotoshootId{get; set;}
    public LocalDate ShootDate{get; set;} //ef ignores this property
}

Is there any supported or recommended approach to using NodaTime with EF Code First?

Community
  • 1
  • 1
smalltowndev
  • 715
  • 8
  • 13
  • 6
    Just in case anyone was expecting me to answer: I've no idea. I don't know how you map custom data types in EF. I'll ping someone who may know though... – Jon Skeet Aug 05 '14 at 20:22
  • Jon: how do you currently store NodaTime values in non-EF database projects? – smalltowndev Aug 06 '14 at 16:19
  • I've done this with [Noda Time in RavenDB](https://github.com/mj1856/RavenDB-NodaTime), and it woks only because RavenDB supports extending its serialization and type conversion. I looked into EF, but I ran into exactly the problem that Colin described. – Matt Johnson-Pint Aug 06 '14 at 16:39
  • Well I don't actually develop .NET applications, so the simple answer is "I don't" :) But if I did, there are lots of options to consider, based on the context - what Noda Time types I was using, the database, what other clients were using the database, etc. I thought I'd got a bit of user guide about it somewhere, but I can't find it now... – Jon Skeet Aug 06 '14 at 16:39

4 Answers4

22

Until custom primitive type persistence is natively supported in Entity Framework, a common work around is to use buddy properties.

For each custom primitive within your domain model, you create an associated mapped primitive to hold the value in a format supported by Entity Framework. The custom primitive properties are then calculated from the value of their corresponding buddy property.

For example:

public class Photoshoot
{
    // mapped
    public Guid PhotoshootId{get; set;}

    // mapped buddy property to ShootDate
    public DateTime ShootDateValue { get; set; }

    // non-mapped domain properties
    public LocalDate ShootDate 
    {
        get { // calculate from buddy property }
        set { // set the buddy property }
    }
}

We use NodaTime in our code first POCO's using exactly this approach.

Obviously this leaves you with a single type acting as both a code first POCO and a domain type. This can be improved at the expense of complexity by separating out the different responsibilities into two types and mapping between them. A half-way alternative is to push the domain properties into a subtype and make all mapped buddy properties protected. With a certain amount of wanging Entity Framework can be made to map to protected properties.

This rather splendid blog post evaluates Entity Framework support for various domain modelling constructs including encapsulated primitives. This is where I initially found the concept of buddy properties when setting up our POCO's: http://lostechies.com/jimmybogard/2014/04/29/domain-modeling-with-entity-framework-scorecard/

A further blog post in that series discusses mapping to protected properties: http://lostechies.com/jimmybogard/2014/05/09/missing-ef-feature-workarounds-encapsulated-collections/

Matt Caton
  • 3,473
  • 23
  • 25
  • This is the method I have been using, it just feels "messy". I've made little extension methods for serializing a LocalDate to/from an integer (yyyymmdd) and have that in a backing field named something like ShootDateSerial. It's too bad EF can't handle type conversion. – smalltowndev Aug 06 '14 at 16:18
  • 1
    Unfortunately, this is the only viable approach, for the reason Colin described. – Matt Johnson-Pint Aug 06 '14 at 16:43
  • 1
    Quite agree - it really is messy. However, the more I use Entity Framework the more I stumble across missing features that I've come to expect from my experience with other ORM's. Perhaps I'm just punch drunk to it now but I've learnt to just accept it for what it can do. – Matt Caton Aug 07 '14 at 09:23
  • 1
    You also can use auto mapper to map POCO/DAL class having supported by EF primitive types to Domain class having types such as Noda. – abatishchev Aug 09 '14 at 04:31
  • 1
    Is there a way to do it with a nullable DateTime? Bec NodaTime LocalDate is a struct. – A_Arnold Oct 10 '18 at 16:11
  • I believe you could use a private backing field and map that to DB and have only one public property. – Code Name Jack Sep 15 '21 at 12:47
12

EF Core 2.1 has a new feature Value Conversions, which is exactly for this scenario.

//OnModelCreating
builder.Entity<MyEntity>
       .Property(e => e.SomeInstant)
       .HasConversion(v => v.ToDateTimeOffset(), v => Instant.FromDateTimeOffset(v));

.HasConversion has some other overloads to make this logic re-useable, for example you can define your own ValueConverter.

Cheng Chen
  • 42,509
  • 16
  • 113
  • 174
  • 2
    This method has a severe drawback: EFCore is so far incapable of comparing the custom types values mapped using ValueConvertor at the data store (at least SQL), so it just loads everything into memory and filters there. See https://github.com/aspnet/EntityFrameworkCore/issues/11156 – jahav Feb 22 '19 at 23:38
  • @jahav Yes that's expected since the value converter can be very complex and can't be translated to sql statements. Note that using buddy properties also has this problem. We should always be careful with this. – Cheng Chen Feb 25 '19 at 08:00
  • @DannyChen isn't the whole point of ValueConverter getting a value that can be translated to/from an SQL statement? As I see it, basic comparisons should be really straightforward to implement. Maybe it was more a time constraint on the EF Core team part? – Ortiga Feb 28 '19 at 17:41
  • @Ortiga Imagine you have a very complex method that converts a `string` to `int` (and another method to convert it back), they are used in `.HasConversion` method, it's impossible to convert the two complex method to sql. – Cheng Chen Mar 06 '19 at 03:58
  • 1
    @ChengChen I think you're missing the point. You don't need to convert the body of the complex method to SQL. You convert the value using the method on code, and only the output of the ValueConverter will be used on the SQL. – Ortiga Apr 02 '19 at 12:14
  • @ChengChen suppose your complex ValueConverter converts the string "foo" to int 1. The linq `.Where(entity => entity.Bar == "foo")` can be converted to sql `WHERE [Table].[Bar] = 1`. Obviously there will be limitations and corner cases, but for simple cases like this, it could work. – Ortiga Apr 02 '19 at 12:45
11

No "clean" way that I'm aware of because EF, as of this writing, doesn't have a mechanism for simple type conversion like you see in NHibernate (IUserType). A real limitation in EF as an ORM which causes me to change my domain to suit my ORM.

Colin Bowern
  • 2,152
  • 1
  • 20
  • 35
2

There is a provider specific way that works with Postgres (Npgsql).

Install the library

dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL.NodaTime 

And then while configuring DbContext, use this,

services.AddDbContext<PhotoshootDbContext>(opt =>opt.UseNpgsql(Configuration.GetConnectionString("ConnectionString"), o => o.UseNodaTime()));

There are some third party libraries for other providers too.

Code Name Jack
  • 2,856
  • 24
  • 40