16

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.

Ivan Stoev
  • 195,425
  • 15
  • 312
  • 343
jacobohunter
  • 363
  • 1
  • 4
  • 16
  • Rewrite the question using proper sentences and explain what you mean by "partial updates" – Panagiotis Kanavos Apr 19 '18 at 13:25
  • Have you examined what EF generates when you change 6 out of 10 properties? – H H Apr 19 '18 at 13:54
  • I think you can use `using System.ComponentModel;` `[ReadOnly(true)]` for these properties – Vivek Nuna Apr 19 '18 at 13:58
  • @PanagiotisKanavos I clearly state in my example updating 6 out of 10 properties how else do you want me to mention a partial update – jacobohunter Apr 19 '18 at 14:17
  • @HenkHolterman What do you mean? If you manually change the 6 out of 10 properties and then call save changes it will update that resource. and leave the other 4 alone like I explained in my question. – jacobohunter Apr 19 '18 at 14:19
  • @jacobohunter when two people ask you the same thing, you probably haven't explained it well. Especially when their combined rep reaches 250K. Assume they know what EF is. Are you *SURE* that EF will generate an UDPATE statement that modifies *all fields*? Did you check this with SQL Profiler or did you inspect the generated statement? And why would you care? Partial update is **not** the same as write-once. In fact, write-once may not have meaning for database and hence EF Core. – Panagiotis Kanavos Apr 19 '18 at 14:21
  • Who's going to write the first value in the *database*? The INSERT statement? Something else? You could create an INSTEAD OF trigger perhaps that ignores updates to specific columns. Or you could mark your properties as read only. Anyway, explain what the actual problem is, not how you think it can be solved – Panagiotis Kanavos Apr 19 '18 at 14:27
  • EF will update with the above code if it's able to find the object in the database. Whenever you load an object from the database in memory with EF and you modify that object it will be saved whenever you call SaveChanges() – jacobohunter Apr 19 '18 at 14:48
  • I consider [this](https://stackoverflow.com/a/30824229/861716) a duplicate. The same considerations apply. – Gert Arnold Apr 19 '18 at 14:50
  • No these are two different questions @GertArnold. I want to know if there is a better way to now to perform an update on a resource partially. If you want to update 6 out of the 10 properties what is the best way? You can retrieve the object in memory and set the properties line by line and have 6-7 lines of code to update that object. I want to know if there is a better way to do that because essentially I just want to ignore updates on a few properties – jacobohunter Apr 19 '18 at 14:56
  • @GertArnold I know that but isn't that the equivalent of Update() the problem with that is it will update the entire object from whatever what was passed in. So if you pass in the object I don't want to update 3 of the properties as they are are one time generated. If you do that method you will end up over riding the db values such as deleted flags/ date time stamps. By the way I do appreciate the activity though in trying to answer this :) – jacobohunter Apr 19 '18 at 15:02

5 Answers5

21

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.

Ivan Stoev
  • 195,425
  • 15
  • 312
  • 343
  • You are a legend, let me verify and I'll mark as answer. – jacobohunter Apr 19 '18 at 17:20
  • 1
    Everyone should up vote this answer, I think property generation features are under rated and not talked enough in the ef core community. – jacobohunter Apr 19 '18 at 20:03
  • I'm on EF Core v5.0.0 and I needed to set State to modified (e.g. _writeContext.Entry(entity).State = EntityState.Modified;) for the CurrentValues.SetValues to save to the DB. Without it, the save was only applying to the entity object. I'm not sure if this is a bug or meant to be a feature but I'm pretty sure we've never had to do this before and I would think 99% of the time if you are doing SetValues you want to commit those changes to the DB. – jspinella Dec 04 '20 at 21:10
  • @jspinella What you are describing is unrelated to the functionality discussed in this thread, so consider posting it as separate question. But AFAIK, the behavior of `SetValues` is not supposed to change - it's always been a "smart" update (only the properties with different value from original) vs "forced" update performed when you use `State = Modified` or `Update` method, which blindly update everything and is supposed to be used in disconnected scenarios where you don`t have original values. Note that `SetValues` might not trigger table update if all the values are the same as original. – Ivan Stoev Dec 05 '20 at 10:52
  • ... But that's how it has been from the very beginning. If something has changed in 5.0, I'm pretty sure it is not intentional and most likely is a bug for a specific scenario. So, create your own post (question), provide repro and we can see what's the case. – Ivan Stoev Dec 05 '20 at 10:56
  • I might do that. I didn't necessarily need partial update in my scenario more of a "anticipating future functionality" thing. Those who need partial update are presumably SOL. – jspinella Dec 06 '20 at 15:17
7

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.

koryakinp
  • 3,989
  • 6
  • 26
  • 56
  • How to get the generated sql script? – Vivek Nuna Apr 19 '18 at 14:27
  • @vivek nuna run SQL Server Profiler – koryakinp Apr 19 '18 at 14:27
  • @viveknuna this answer just checked the script and proves what we mentioned in the comments. Partial updates are the default behaviour. I suspect you ask for something different though – Panagiotis Kanavos Apr 19 '18 at 14:28
  • I tried to run SQL profiler, but it’s complicated. Could you please update the questions with steps – Vivek Nuna Apr 19 '18 at 14:29
  • @viveknuna you can also log generated queries eg with `context.Database.Log = Console.Write;`. In any case, partial updates are the default. Why do you doubt this? – Panagiotis Kanavos Apr 19 '18 at 14:30
  • @viveknuna if you want to know how it works, EF keeps track of the original entitye values. When you call `SaveChanges` it retrieves the current values from the entities, compares them to the original values and generates the appropriate statements for all detected changes – Panagiotis Kanavos Apr 19 '18 at 14:31
  • @koryakinp thanks for the attempted answer but that's not what I'm looking for, I know you can write that statement and if you want to update say 6 more properties and ignore 4 you will have to write 6 more lines of code. Is there a better way to write code for this? – jacobohunter Apr 19 '18 at 14:47
  • @jacobohunter I guess, what you are really looking for is how to map some DTO object to your entity to perform an update. You can use AutoMapper library for that, but that is really out of the scope of your question. – koryakinp Apr 19 '18 at 16:09
  • @koryakinp yes exactly the use case the problem is the dto object will only have 6 fields to provide. So the other 4 will be null when mapped. So the problem is if you set the current values from the object it will set the other 4 properties as null – jacobohunter Apr 19 '18 at 16:53
  • @jacobohunter, that need to be handled by mapping configuration and has nothing to do with EF. – koryakinp Apr 19 '18 at 17:43
2

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.

Mohammad
  • 1,498
  • 2
  • 12
  • 15
0

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://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.mutablepropertyextensions.setaftersavebehavior?view=efcore-3.1

Skowronek
  • 308
  • 3
  • 10
-1

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;
            });


        }
    }