158

What is the best approach to update database table data in Entity Framework Core?

  1. Retrieve the table row, do the changes and save
  2. Use keyword Update in DB context and handle exception for item not exist

What are the improved features we can use over EF6?

2
  • It all depends on specific requirements. Without knowing these, we can only give opinions. Jun 6, 2022 at 9:22
  • For newcomers, if you want to jump to the summary, see my answer.
    – D G
    Sep 30, 2022 at 10:30

10 Answers 10

184

To update an entity with Entity Framework Core, this is the logical process:

  1. Create instance for DbContext class
  2. Retrieve entity by key
  3. Make changes on entity's properties
  4. Save changes

Update() method in DbContext:

Begins tracking the given entity in the Modified state such that it will be updated in the database when SaveChanges() is called.

Update method doesn't save changes in database; instead, it sets states for entries in DbContext instance.

So, We can invoke Update() method before to save changes in database.

I'll assume some object definitions to answer your question:

  1. Database name is Store

  2. Table name is Product

Product class definition:

public class Product
{
    public int? ProductID { get; set; }
    
    public string ProductName { get; set; }
    
    public string Description { get; set; }
    
    public decimal? UnitPrice { get; set; }
}

DbContext class definition:

public class StoreDbContext : DbContext
{
    public DbSet<Product> Products { get; set; }
    
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer("Your Connection String");

        base.OnConfiguring(optionsBuilder);
    }
    
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Order>(entity =>
        {
            // Set key for entity
            entity.HasKey(p => p.ProductID);
        });
        
        base.OnModelCreating(modelBuilder);
    }
}

Logic to update entity:

using (var context = new StoreDbContext())
{
        // Retrieve entity by id
        // Answer for question #1
        var entity = context.Products.FirstOrDefault(item => item.ProductID == id);
        
        // Validate entity is not null
        if (entity != null)
        {
            // Answer for question #2

            // Make changes on entity
            entity.UnitPrice = 49.99m;
            entity.Description = "Collector's edition";
            
            /* If the entry is being tracked, then invoking update API is not needed. 
              The API only needs to be invoked if the entry was not tracked. 
              https://www.learnentityframeworkcore.com/dbcontext/modifying-data */
            // context.Products.Update(entity);
            
            // Save changes in database
            context.SaveChanges();
        }
}
4
  • 1
    Thank you . This is a good example on how to use 2. Use Key word Update in db context and handle exception for item not exist . I am more interested on selecting which one to use as a best practice.
    – CuriousGuy
    Oct 15, 2017 at 13:33
  • Why did you use nullable int? for ProductID? It becomes optional primary key? Aug 20, 2019 at 13:28
  • 11
    In fact, the line context.Products.Update is redundant since the entity will be tracked once you retrieve it from the context. Here's an excellent overview of the different approaches: learnentityframeworkcore.com/dbcontext/modifying-data
    – Johan Maes
    Sep 5, 2019 at 13:47
  • Does this somehow protects from concurrent modification of the entity? meaning if some other thread saves changes to entity between this threads getting and saving the entity? Jul 3, 2022 at 11:14
103

According to Microsoft docs:

the read-first approach requires an extra database read, and can result in more complex code for handling concurrency conflict

However, you should know that using Update method on DbContext will mark all the fields as modified and will include all of them in the query. If you want to update a subset of fields you should use the Attach method and then mark the desired field as modified manually.

context.Attach(person);
context.Entry(person).Property(p => p.Name).IsModified = true;
context.SaveChanges();
5
  • 37
    Just small edition, now there is more strongly typed version of this API: context.Entry(person).Property(p => p.Name).IsModified = true;
    – Guru Stron
    Jul 31, 2018 at 20:15
  • 4
    Also can simply do context.Entry(person).State = EntityState.Modified; Dec 29, 2018 at 5:20
  • 1
    What does this context.Entry(person).State = EntityState.Modified; mean? If I modify multiple fields, should I make any changes to this line?
    – user989988
    Jan 15, 2019 at 16:53
  • 2
    Good point ehsan jan! ;D Also, if the entity is updated by user and you don't know/care whcih fields are modified, you can use _context.Attach(person).State = EntityState.Modified; to indicate this entity shall be updated at SaveChanges method. Feb 14, 2019 at 11:53
  • Great catch. So this requires the context to "stick around", so it knows about entities in the context. I just tested this..and if you try to edit an non existing, it gives an exception ! :) One or more errors occurred. (Attempted to update or delete an entity that does not exist in the store.) Dec 11, 2019 at 21:46
50
public async Task<bool> Update(MyObject item)
{
    Context.Entry(await Context.MyDbSet.FirstOrDefaultAsync(x => x.Id == item.Id)).CurrentValues.SetValues(item);
    return (await Context.SaveChangesAsync()) > 0;
}
4
  • I think this method works really well, but I believe the post is down at bottom because it could have used more context. but like it!
    – Mr. Kraus
    Sep 14, 2019 at 17:57
  • 1
    I like it too! @Mr. Krause wouldn't the post be low (so far) on account of it being only a few days old?
    – Wellspring
    Sep 17, 2019 at 2:50
  • 1
    I'm getting "Violation of PRIMARY KEY constraint 'PK_Offer'. Cannot insert duplicate key in object 'dbo.Offer'. " from this. Nov 16, 2019 at 18:31
  • This has two DB calls, and can be improved. The second answer is actually an efficient way.
    – Shahbaaz
    Feb 11, 2023 at 16:17
29

It's super simple

using (var dbContext = new DbContextBuilder().BuildDbContext())
{
    dbContext.Update(entity);
    await dbContext.SaveChangesAsync();
}
24

Assume we have an entity Student and AppDbContext as follows.

class Student
{
    public int Id { get; set; }
    public string Name { get; set; } = default!;
    public int Age { get; set; }
}


public class AppDbContext : DbContext
{
    public AppDbContext(DbContextOptions<AppDbContext> opts) : base(opts) { }

    public DbSet<Student> Students { get; set; }
}

Version A

  • CurrentValues can only work for a tracked entity (found).
  • Only the changed properties are marked as Modified.
  • Automatic property mapping that is useful when using type parameter TEntity instead of a fixed type Student.
async Task Edit_A(int id, Student incoming, AppDbContext db)
{
    if (await db.Students.FindAsync(id) is Student found)
    {
        db.Entry(found).CurrentValues.SetValues(incoming);

        await db.SaveChangesAsync();
    }
}

Version B

  • It works only on a tracked entity (found).
  • It is not necessary to map all properties because only the changed properties are marked as Modified.
  • Manual property mapping so we cannot not use generic type parameter.
async Task Edit_B(int id, Student incoming, AppDbContext db)
{
    if (await db.Students.FindAsync(id) is Student found)
    {
        found.Name = incoming.Name;
        found.Age = incoming.Age; 
        

        await db.SaveChangesAsync();
    }
}

Version C

  • Update() works only on an untracked entity (incoming) and makes it tracked. Untracking found before invoking Update(incoming) is mandatory because only one entity can be tracked with the given primary key.
  • All properties (including unchanged ones) are marked as Modified. It is less efficient.
  • Automatic property mapping that is useful for generic type parameter.
async Task Edit_C(int id, Student incoming, AppDbContext db)
{
    if (await db.Students.FindAsync(id) is Student found)
    {
        db.Students.Entry(found).State = EntityState.Detached;
        db.Students.Update(incoming);
        await db.SaveChangesAsync();
    }
}

Version D

  • It is the same as version C. I rewrite again below for the sake of completeness.
  • It works only on an untracked entity (incoming) and makes it tracked. Untracking found is mandatory because only one entity can be tracked with the given primary key.
  • All properties (including unchanged ones) are marked as Modified. It is less efficient.
  • Automatic property mapping that is useful for generic type parameter.
async Task Edit_D(int id, Student incoming, AppDbContext db)
{
    if (await db.Students.FindAsync(id) is Student found)
    {
        db.Students.Entry(found).State = EntityState.Detached;
        db.Students.Entry(incoming).State = EntityState.Modified;
        await db.SaveChangesAsync();
    }
}

Version E

  • It works only on an untracked entity (incoming) and makes it tracked. Untracking found is mandatory because only one entity can be tracked with the given primary key.
  • It is not necessary to map all properties because only properties (including unchanged ones) marked with IsModified=true will be updated. It is less efficient if you mark IsModified=true for unchanged properties.
  • Manual property mapping so we cannot not use generic type parameter.
async Task Edit_E(int id, Student incoming, AppDbContext db)
{
    if (await db.Students.FindAsync(id) is Student found)
    {
        db.Students.Entry(found).State = EntityState.Detached;
        db.Students.Entry(incoming).Property(s => s.Name).IsModified = true;
        db.Students.Entry(incoming).Property(s => s.Age).IsModified = true;
        await db.SaveChangesAsync();
    }
}

I set it as a Community Wiki, feel free to edit as many as you want.

11

Microsoft Docs gives us two approaches.

Recommended HttpPost Edit code: Read and update

This is the same old way we used to do in previous versions of Entity Framework. and this is what Microsoft recommends for us.

Advantages

  • Prevents overposting
  • EFs automatic change tracking sets the Modified flag on the fields that are changed by form input.

Alternative HttpPost Edit code: Create and attach

an alternative is to attach an entity created by the model binder to the EF context and mark it as modified.

As mentioned in the other answer the read-first approach requires an extra database read, and can result in more complex code for handling concurrency conflicts.

4

After going through all the answers I thought i will add two simple options

  1. If you already accessed the record using FirstOrDefault() with tracking enabled (without using .AsNoTracking() function as it will disable tracking) and updated some fields then you can simply call context.SaveChanges()

  2. In other case either you have entity posted to server using HtppPost or you disabled tracking for some reason then you should call context.Update(entityName) before context.SaveChanges()

1st option will only update the fields you changed but 2nd option will update all the fields in the database even though none of the field values were actually updated :)

3

A more generic approach

To simplify this approach an "id" interface is used

public interface IGuidKey
{
    Guid Id { get; set; }
}

The helper method

public static void Modify<T>(this DbSet<T> set, Guid id, Action<T> func)
    where T : class, IGuidKey, new()
{
    var target = new T
    {
        Id = id
    };
    var entry = set.Attach(target);
    func(target);
    foreach (var property in entry.Properties)
    {
        var original = property.OriginalValue;
        var current = property.CurrentValue;

        if (ReferenceEquals(original, current))
        {
            continue;
        }

        if (original == null)
        {
            property.IsModified = true;
            continue;
        }

        var propertyIsModified = !original.Equals(current);
        property.IsModified = propertyIsModified;
    }
}

Usage

dbContext.Operations.Modify(id, x => { x.Title = "aaa"; });
1
  • Where I need to implement the interface GuidKey ? with this solution the static method is not valid on the repository Oct 29, 2021 at 14:26
0

Personally I see the operation that you are doing is an upsert operation, where if data already exist we update, else we insert. There is one good library from Flexlab to support Upsert operation with this syntax

var country = new Country
{
    Name = "Australia",
    ISO = "AU",
    Created = DateTime.UtcNow,
};


await DataContext.Upsert(country)
    .On(c => c.ISO)
    .UpdateColumns(c => new Country
    {
        Name = "Australia"
        Updated = DateTime.UtcNow,
    })
    .RunAsync();

The code will check for the property Country.ISO in the table. If it does not exist yet it will insert new row altogether into the table. Else if any row with the same Country.ISO already exist it will update columns Country.Name and Country.Updated for that row.

This method is very fast because we only do one call to the database instead of two calls to check if the data already exist before we updating or inserting the data.

Note that this answer does not apply to you if your intend is not to do the Upsert operation

0
0

Even though this is an old question and the answer(s) are technically correct, I would like to point out that certain decisions on how we architect our software solution do affect the way we approach and use ORM(s) such as EF.

As a result, they affect what we accept as the best answer, too.

In other words, why Christian's answer is not the accepted one?

In a layered architecture that follows basic DDD, the Product class would probably be a domain model with methods to implement (any) business logic. In this case, the UnitPrice property would be set or updated by such a method, say, CalculatePrice instead of directly assigning a value to it:

public class Product
{
    public int ProductID { get; private set; }

    public decimal? UnitPrice { get; private set; }
    ...
    public Product CalculatePrice(...)
    {
        UnitPrice = ...;
    }
}

Having such a domain model, do you really need to ask which of its properties to update in the data store? Probably not. All you need is a reference to the tracked object to update. In the application layer the code becomes:

var product = repository.Find(productId);

if (product != null)
{
    product.CalculatePrice(...);

    repository.Update(product);
}

The implementation of the repository's Update method is:

public Product Update(Product entity)
{
    dbContext.Update(entity);

    dbContext.SaveChanges();

    return entity;
}

With regard to the OP, the above example showcases how design decisions affect both the meaning of our questions ("[...]best approach to update database") and which answer we mark as the accepted one.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.