proggrock proggrock - 1 month ago 19x
C# Question

EF core computed properties marked as readonly

Background: I'm overriding the SaveChanges() method to automatically generate a LastUpdatedDate whenever the "Item" entity is added or updated.


public DateTime? LastUpdated { get; set; }


protected override void OnModelCreating(ModelBuilder modelBuilder)
// generated value
.Property(b => b.LastUpdated)


public override int SaveChanges()
var now = DateTime.UtcNow;

foreach (var item in ChangeTracker.Entries<Item>()
.Where(e => e.State == EntityState.Added || e.State == EntityState.Modified))
item.Property("LastUpdated").CurrentValue = now;

return base.SaveChanges();

The problem I'm having is whenever SaveChanges() is called I get this exception:

An exception of type 'System.InvalidOperationException' occurred in EntityFramework.Core.dll but was not handled in user code.

Additional information: The property 'LastUpdated' on entity type 'Item' is defined to be read-only after it has been saved, but its value has been modified or marked as modified.

To workaround this, I've had to set IsReadOnlyBeforeSave and IsReadOnlyAfterSave
to false, as shown below:

.Property(b => b.LastUpdated)
.Metadata.IsReadOnlyBeforeSave = false;
.Property(b => b.LastUpdated)
.Metadata.IsReadOnlyAfterSave = false;


Is this the correct way to setup computed properties in EF Core?

Additionally, can I prevent LastUpdated from being defined as "readonly" in the first place?


Is this the correct way to setup computed properties in EF Core?

The documentation is quite clear on this:

Value generated on add or update means that a new value is generated every time the record is saved (insert or update).


How the value is generated for added and updated entities will depend on the database provider being used. (...) if you specify that a DateTime property is generated on add or update, then you must setup a way for the values to be generated (such as a database trigger).

The name of the annotation --"DatabaseGeneratedOption"-- might have revealed something along these lines.

So if you want to use this pattern you should set up a trigger in the database that sets the field value on insert and each update. By using the annotation [DatabaseGenerated] or the fluent API .ValueGeneratedOnAddOrUpdate() (no need to do both), EF is signaled that it should read the value from the database after saving changes.

As for the IsReadOnlyBeforeSave property. The only documentation so far is the XML doc on the property in EF's source code:

Gets or sets a value indicating whether or not this property can be modified before the entity is saved to the database. If true, an exception will be thrown if a value is assigned to this property when it is in the Added state.

As far as I can see, you may want to set this value to true to eliminate any expectation that setting this property is useful (because it isn't). Likewise, you can set IsReadOnlyAfterSave to throw an exception when the property is set in existing (not Added) entities.

If you don't want database-generated values, you can remove the annotation and assign the values as you do now.