'What is the best way to perform partial updates in EF core and never update certain properties?

I know you can do something like var myObj = _db.MyTable.FirstOrDefault(x=>x.Id==id) and then update myObj property by property that you want to update but is there a better way to update say 6 out of 10 properties of myObj and leave the other 4 alone or have them marked as a way that they are only set once and never updateable from ef core?

    public class MyObject
{
    public string Id { get; set; }
    public string Prop1 { get; set; }
    public string Prop2 { get; set; }
    public string Prop3 { get; set; }
    public string Prop4 { get; set; }
    public string Prop5 { get; set; }
    public string Prop6 { get; set; }
    public string Prop7 { get; set; }
    public string Prop8 { get; set; }
    public string Prop9 { get; set; }

}

       public void UpdateObj(MyObject ojToUpdate)
    {
        //Is there a better way to write this function if you only want to update a set amount of properties
        var myObj = _db.MyObject.First(x=>x.Id==ojToUpdate.Id);
        myObj.Prop1 = objToUpdate.Prop1;
        myObj.Prop2 = objToUpdate.Prop2;
        myObj.Prop3 = objToUpdate.Prop3;
        myObj.Prop4 = objToUpdate.Prop4;
        myObj.Prop5 = objToUpdate.Prop5;
        myObj.Prop6 = objToUpdate.Prop6;
        _db.SaveChanges();
    }

Obviously you can write something like _db.MyObject.Update(objToUpdate). The problem with this statement is the user can update prop 4/5/6 which I don't want them to update. Yes I know you can write _db.Entry(myObj).CurrentValues.SetValues(objToUpdate) and then call save changes but that will over ride properties that i want to be generated once and never modified again.

Thanks ahead of time.



Solution 1:[1]

Starting with EF Core 2.0, you can use IProperty.AfterSaveBehavior property:

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.

What you need is the Ignore option. At the time of writing there is no dedicated fluent API method for that, but Setting an explicit value during update contains an example how you can do that.

Taking your example, something like this:

modelBuilder.Entity<MyObject>(builder =>
{
    builder.Property(e => e.Prop7).Metadata.AfterSaveBehavior = PropertySaveBehavior.Ignore;
    builder.Property(e => e.Prop8).Metadata.AfterSaveBehavior = PropertySaveBehavior.Ignore;
    builder.Property(e => e.Prop9).Metadata.AfterSaveBehavior = PropertySaveBehavior.Ignore;
});

Now both

public void UpdateObj(MyObject objToUpdate)
{
    var myObj = _db.MyObject.First(x => x.Id == objToUpdate.Id);
    _db.Entry(myObj).CurrentValues.SetValues(myObjToUpdate);
    _db.SaveChanges();
}

and

public void UpdateObj(MyObject objToUpdate)
{
    _db.Update(myObjToUpdate);
    _db.SaveChanges();
}

will ignore Prop7, Prop8 and Prop9 values of the passed myObjToUpdate.

Update (EF Core 3.0+) The aforementioned property has been replaced with GetAfterSaveBehavior and SetAfterSaveBehavior extension methods.

Solution 2:[2]

If you have an entity:

public class Person
{
    public Guid Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
    public string Address { get; set; }
}

And you run:

var p = ctx.Person.First();
p.Name = "name updated";
ctx.SaveChanges();

EF will generate the following SQL statement:

enter image description here

You can verify it using SQL Server Profiler, the same is true if you update 6/10 properties.

Solution 3:[3]

You can also in your dbContext inside the OnModelCreating use before mentioned AfterSaveBehavior like this:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{

modelBuilder.Entity<someobject>(builder =>
{
    builder.Property(i => i.CreatedAt).Metadata.AfterSaveBehavior = PropertySaveBehavior.Ignore;
});

Now the "CreatedAt" property on "someobject" will be saved first time, and then never modified on future updates.

Solution 4:[4]

As of 3.1, this can be accomplished by using the SetAfterSaveBehavior() extension method:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<someobject>(builder =>
{
    builder.Property(i => i.CreatedAt).Metadata.SetAfterSaveBehavior(PropertySaveBehavior.Ignore);
});

https://docs.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.mutablepropertyextensions.setaftersavebehavior?view=efcore-3.1

Solution 5:[5]

this method will update only fields which of them you want to. If you use context.Update(entity) it will mark all properties to modified, so it will update all of them. But this usage you will send the propertynames, it will mark updated just you have send

  public class UpdateEntity<TEntity> : IUpdateEntity<TEntity> where TEntity : class

    {
        private readonly DataContext _dataContext;

        public UpdateEntity(DataContext dataContext)
        {
            _dataContext = dataContext;
        }

        public void ContextAttach(TEntity entity, List<string> propertyNames)
        {
            _dataContext.Attach(entity);
            propertyNames.ForEach(i =>
            {
                _dataContext.Entry(entity).Property(i).IsModified = true;
            });


        }
    }

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 koryakinp
Solution 3 Mohammad
Solution 4 Skowronek
Solution 5 Ça?lar Can Sar?kaya