noblerare
noblerare

Reputation: 11843

Specify EF Core column/field as read only

I have a SQL Server table with certain fields that are set by the database via default values that, once saved, should never been modified again (e.g. DateCreated).

In the Entity Framework Core 2.1 model builder or classes, how do we "mark" a field as essentially read-only? In other words, I don't want any code to be able to set or overwrite these fields.

Based on my searching, would I add .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity) at the end of .Property()?

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Doohicky>(entity =>
    {
        ... // other fields

        entity.Property(e => e.DateCreated).HasDefaultValueSql("(getdate())");

        ... // other fields
    });
}

Or do I add a [DatabaseGenerated(DatabaseGeneratedOption.Identity)] annotation to the DateCreated field?

public class Doohicky
{
    public DateTime DateCreated {get; set;}
}

Or is there another way entirely?

I want it such that in the future, if anybody decides to write something like this, an error would be thrown.

model.DateCreated = new DateTime();
dbContext.SaveChanges() // errors out

Any insight would be greatly appreciated.

Upvotes: 11

Views: 15012

Answers (3)

Ivan Stoev
Ivan Stoev

Reputation: 205529

The EF Core intended way is to set AfterSaveBehavior property to value other than the default Save:

Gets a value indicating whether or not this property can be modified after the entity is saved to the database.

If Throw, then an exception will be thrown if a new value is assigned to this property after the entity exists in the database.

If Ignore, then any modification to the property value of an entity that already exists in the database will be ignored.

There is no dedicated fluent API yet, so you need to set it directly through mutable property metadata like this:

entity.Property(e => e.DateCreated)
    .HasDefaultValueSql("(getdate())")
    .Metadata.AfterSaveBehavior = PropertySaveBehavior.Throw; // <-- 

Update (EF Core 3.x): Starting with EF Core 3.0, many properties like this have been replaced with Get / Set extension method pairs, so the relevant code now is as follows:

    .Metadata.SetAfterSaveBehavior(PropertySaveBehavior.Throw); 

Upvotes: 18

Mark Meisel
Mark Meisel

Reputation: 861

I've done this in the past with auditable properties such as DateCreated, DateModified, etc. This solution probably isn't ideal for excluding specific properties in various objects (although you could probably do something with a custom attribute, etc.).

I override SaveChanges/Async(), then loop through all the changed objects that the context is tracking. All of my objects use the same base class so I can achieve this through the following:

var changes = ChangeTracker.Entries<BaseEntity>().Where(x => x.State == EntityState.Added || x.State == EntityState.Modified);

With those objects, I loop over them and set some auditable properties, or ignore certain properties if the object isn't new. First, I have a collection of strings which represent property names that I want to exclude. I then loop over the collection and ignore the properties where the property name matches that of the excluded collection. See below:

// A collection of property names which should not be updated
var excludedProperties = new[] { "CreatedBy", "CreatedDateUtc" };

foreach (var change in changes)
{
   // If new, do as you'd like

   // If used, ignore date created
   Array.ForEach(excludedProperties, prop =>
   {
      change.Property(prop).IsModified = false;
   });
}

Upvotes: 1

Mohamed Elrashid
Mohamed Elrashid

Reputation: 8579

[Required, DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime DateCreated {get; set;}

Upvotes: 4

Related Questions