2

I have a .NET Core API which is supposed to update an entity in the database using Entity Framework Core.

When a user edits an existing entry, the edit form only sends back the edited data, not the full entity.

Let's say we have a shop:

public class Shop {
    public int ShopID { get;set;}
    public string Name { get;set;}
    public string Address { get;set;}
}

Now, the user edits the address and saves it. Data sent back to the API will be the ShopID and the Address. However, using the model binding below would set the Name to NULL, which is logical since it hasn't actually been passed in.

[Route("~/shop/[action]")]
public IActionResult Update([FromBody] Shop shop)
{
    _context.Shops.Update(shop);
    _context.SaveChanges();
    return new JsonResult(new { result = true });
}

So, since I don't know which property/ies might be updated (in practice, there's a lot more properties), I need some way of dynamically updating only the fields sent through in the POST request.

Thanks in advance.

Max
  • 137
  • 2
  • 10

2 Answers2

3

DbSet<T> doesn't contain the method Update, so you should firstly load the entity, then change property values, and then call SaveChanges:

[Route("~/shop/[action]")]
public IActionResult Update([FromBody] Shop shop)
{
    var shopData = _context.Shops.Single(s => s.Id == shop.ShopId);

    if (shop.Name != null)
        shopData.Name = shop.Name;

    if (shop.Address != null)
        shopData.Address = shop.Address;

    _context.SaveChanges();
    return new JsonResult(new { result = true });
}

Cause it annoying to check and to copy every property, you can use libraries like Automapper:

[Route("~/shop/[action]")]
public IActionResult Update([FromBody] Shop shop)
{
    var shopData = _context.Shops.Single(s => s.Id == shop.ShopId);

    // Copying properties to existing object
    mapper.Map<Shop, Shop>(shop, shopData);

    _context.SaveChanges();
    return new JsonResult(new { result = true });
}

For skipping null properties see the answer Automapper skip null values with custom resolver

Community
  • 1
  • 1
Mark Shevchenko
  • 7,937
  • 1
  • 25
  • 29
  • So close but still some way to go. The AutoMapper looks great and I had a look but the answer seem to be outdated with the latest version as `.ForAllMembers(opt => opt.Condition(srs => !srs.IsSourceValueNull));` doesn't actually contain IsSourceValueNull. And the other mention of implementing `IValueResolver` doesn't seem to be valid either, as that interface now needs three types `public interface IValueResolver`. Or am I being completely stupid? – Max Aug 26 '16 at 12:39
  • As I can see, there is the answer here: https://github.com/AutoMapper/AutoMapper/issues/1406 `.ForAllMembers(x => x.Condition(y => y != null))` – Mark Shevchenko Aug 26 '16 at 13:25
  • Thanks, looks to be workingQ For reference sake, EF Core actually has the method .Update(). Doesn't seem to be documented in their online docs but definitely works, and is in VS intellisense. – Max Sep 16 '16 at 06:44
  • @Max, Thanks. I'll try find the info, although I can't simply google the method `DbSet.Update`. – Mark Shevchenko Sep 16 '16 at 10:10
0

I didn't test it but the code should be something like below:

var entry = _context.Shops.Update(shop);
foreach (var property in entry.Entity.GetType().GetTypeInfo().DeclaredProperties)
{
      var currentValue = entry.Property(property.Name).CurrentValue;
      if (currentValue == null)
            entry.Property(property.Name).IsModified = false;
}
adem caglin
  • 22,700
  • 10
  • 58
  • 78