简体   繁体   中英

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.

Item.cs

  [DatabaseGenerated(DatabaseGeneratedOption.Computed)]
  public DateTime? LastUpdated { get; set; }

DbContext

  protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
      ...
      // generated value
      modelBuilder.Entity<Item>()
                  .Property(b => b.LastUpdated)
                  .ValueGeneratedOnAddOrUpdate();

     }

    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:

        modelBuilder.Entity<Tray>()
            .Property(b => b.LastUpdated)
            .ValueGeneratedOnAddOrUpdate()
            .Metadata.IsReadOnlyBeforeSave = false; 
        modelBuilder.Entity<Tray>()
            .Property(b => b.LastUpdated)
            .Metadata.IsReadOnlyAfterSave = false;

Question:

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).

Caution

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.

The IsReadOnlyAfterSave flag has been obsoleted and replaced by AfterSaveBehavior .

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Device>()
        .Property<string>("TenantId")
        .HasField("_tenantId")
        //.Metadata.IsReadOnlyAfterSave = true;
        .Metadata.AfterSaveBehavior = Microsoft.EntityFrameworkCore.Metadata.PropertySaveBehavior.Ignore;
}

EF Core 3.x and 5

This is how you set the AfterSaveBehavior in EF Core 5

    protected override void OnModelCreating(ModelBuilder modelBuilder) 
    { 
       modelBuilder.Entity<MyEntity>
         .Property(x => x.DateCreated)
         .Metadata.SetAfterSaveBehavior(PropertySaveBehavior.Save); 
    }

Throw

If an explicit value is set or the value is changed, then an exception will be thrown. This is the default and is why you get an exception - set it to Save or Ignore depending on your situation.

Save

The value set or changed will be sent to the database in the normal way.

Ignore

Any value set or changed will be ignored.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM