EF Core Tips

Make sure to call Update when it is needed!

My last post talked about not calling DbContext.Update or DbSet.Update when it isn't needed. This post presents the opposite: a place where it is not obvious but necessary to call Update.


Getting it wrong...

Here's an example of the kind of code I see which makes this mistake. Can you spot it?

public async Task<IActionResult> OnPostAsync(int id)
{
    // Warning: Don't copy-paste this code. It is wrong.

    var user = new User { Id = id };

    _context.Attach(user);

    if (await TryUpdateModelAsync<User>(
        user,
        "user",
        s => s.Name, s => s.Email))
    {
        await _context.SaveChangesAsync();
        return RedirectToPage("./Index");
    }

    return Page();
}

This code is intended to update an existing entity in the database. The sequence of events is supposed to be:

  1. Create a new, empty User instance. (This is sometimes called a "stub".)
  2. Attach it so EF will start tracking changes.
  3. Use TryUpdateModelAsync from ASP.NET Core to set values into the entity instance.
  4. EF will detect that these values have been set and mark the properties as modified.
  5. SaveChanges updates the database with these changes.

Sounds good, right? But what happens if the email coming back from the client is null? In this case setting Email to null in TryUpdateModelAsync has no affect--it's already null. This means there is no change for EF to detect, which means the Email property is not marked as modified and will not be updated in the database.

So, with this code, the Email will never be updated to NULL in the database. Instead it will retain its current value.


Fixing it...

Just changing Attach to Update in the above code will fix the issue. Update works the same as Attach except that it sets all properties as modified instead of unchanged.

public async Task<IActionResult> OnPostAsync(int id)
{
    var user = new User { Id = id };

    _context.Update(user); // Use Update here instead of Attach

    if (await TryUpdateModelAsync<User>(
        user,
        "user",
        s => s.Name, s => s.Email))
    {
        await _context.SaveChangesAsync();
        return RedirectToPage("./Index");
    }

    return Page();
}

Email will now always be updated even if it is set to null, since Update marks all properties as modified.


Slightly better still...

So why isn't this the code that I showed in the previous post? Notice in the code above that Id is set before calling Update. This is because EF needs to know the primary key value in order to track the entity correctly. This can also be true for some other property types--for example, alternate keys.

If we instead use the code from the previous post, then TryUpdateModelAsync can set every required property before EF starts tracking the entity. Ultimately all properties are marked as modified by the Update call, and so all values will be saved.

public async Task<IActionResult> OnPostAsync(int id)
{
    var user = new User();

    if (await TryUpdateModelAsync<User>(
        user,
        "user",
        s => s.Id, s => s.Name, s => s.Email))
    {
        _context.Update(user);

        await _context.SaveChangesAsync();
        return RedirectToPage("./Index");
    }

    return Page();
}

Of course, you should still consider the trade-offs from the previous post when using this code.


This page is up-to-date as of January 18th, 2020. Some things change. Some things stay the same. Use your noggin.