entity-framework-coreentity-framework-core-3.1

How can I prevent the default value of DateTimeOffset from being inserted?


I'm using Entity Framework Core 3.1.7 and created an entity called Event, which I set up like this:

public class Event
{
    public long Id { get; set; }
    public DateTimeOffset FirstOccurred { get; set; }
}

The entity configuration looks like this:

builder.Property(e => e.FirstOccurred)
    .IsRequired();

I use my dbContext to persist the entity like this:

await dbContext.Events.AddAsync(new Event());

In this scenario, I was incorrectly expecting that an exception would be thrown at the Database level because the value can't be null.

What actually happens is: the entity is happily persisted with FirstOccurred set to 0001-01-01T00:00:00+00:00

This makes sense, because the default value of DateTimeOffset is used.

Now my question: How could I improve my design to prevent this default value from being inserted?

Some ideas I had already:

  1. Leave the above code as is, but make sure that wherever the entity is used, I'm setting the values correctly. Downside: no guarantee that this will be applied consistently in a team over time.
  2. Make DateTimeOffset nullable, which in the above AddAsync() call would actually cause an SQL exception. Downside: At first glance, DateTimeOffset? FirstOccurred might be confusing because the actual DB constraints don't allow null
  3. Remove set; for FirstOccurred and create a constructor that requires this property to be set, e.g. new Event(DateTimeOffset.Now)

Solution

  • I think you're on the right track with your last idea.

    Remove set; for FirstOccurred and create a constructor that requires this property to be set, e.g. new Event(DateTimeOffset.Now)

    It doesn't make sense to track an Event without the timestamp and it certainly doesn't make sense to use the default value for the timestamp.

    Changing your model to require a value for the timestamp ensures that you are not writing default data to the record and prevents confusion from seeing a nullable model field when the corresponding table column is non-nullable.

    public class Event {
        public Event (DateTimeOffset firstOccurred) { FirstOcurred = firstOcurred; }
        public long Id { get; set; }
        public DateTimeOffset FirstOccurred { get; set; }
    }
    

    Just a note from the documentation, don't remove the set; accessor, just mark it private if you don't want the value to change after construction.

    Once properties are being set via the constructor it can make sense to make some of them read-only. EF Core supports this, but there are some things to look out for:

    Properties without setters are not mapped by convention. (Doing so tends to map properties that should not be mapped, such as computed properties.) Using automatically generated key values requires a key property that is read-write, since the key value needs to be set by the key generator when inserting new entities. An easy way to avoid these things is to use private setters.

    Of course, you could also maintain the flexibility of the parameter-less constructor by overriding the default value for the property.

    public class Event {
        public long Id { get; set; }
        public DateTimeOffset FirstOccurred { get; set; } = DateTimeOffset.Now;
    }