Kobarin's Development Blog

C#やASP.NETなどについての記録です。

SaveChanges()による特定フィールドの更新

EntityFrameworkを使って更新する一般的な事例として、よく以下のようなコードを見かけます。

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(Product product)
{
  if(ModelState.IsValid)
  {
    db.Entry(product).State = EntityState.Modified;
    db.SavaChanges();
    
    return RedirectToAction("List");
  }
  return View(product);
}

確かに全フィールドを対象としたフル更新型のフォームではこれで正しいですが、「価格だけの更新」「フラグの項目だけ更新」といったように特定フィールドだけの部分更新をしたい場面も多々あると思います。
以下の回答を参考にしたので、解説いたします。
stackoverflow.com

価格フィールドに限定した更新の例

まず上記コードを基に部分更新に作り変えたのが以下のコードです。

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult EditPrice([Bind(Include = "ProductId,ListPrice")] Product product)
{
  if(ModelState.IsValid)
  {
    db.Products.Attach(product);  // Attachが必要。後述。
    db.Entry(product).Property(m => m.ListPrice).IsModified = true; // 対象フィールドを更新指定
    db.SavaChanges();
    
    return RedirectToAction("List");
  }
  return View(product);
}

こんな感じで、数行の変更で部分更新が完成です。

説明

忘れないでほしいのは

db.Products.Attach(product);

の部分で、これを書かないと以下のようなエラーが出ます。

Member 'IsModified' cannot be called for property 'ListPrice' because the entity of type 'Product' does not exist in the context. To add an entity to the context call the Add or Attach method of DbSet.

ちょっと意味がわかりにくですが簡単に説明すると、
「いきなり『db.Entry(product)』してもエンティティが出来ないので、AddかAttachしてくれよ」
みたいな意味のようです。
今回は追加ではなく更新なのでAttachになります。

次の行で更新対象のフィールドを指定しています。

db.Entry(product).Property(m => m.ListPrice).IsModified = true;

もちろん、複数ある場合は同じように

db.Entry(product).Property(m => m.ListPrice).IsModified = true;
db.Entry(product).Property(m => m.StandardCost).IsModified = true;
db.Entry(product).Property(m => m.Name).IsModified = true;

のように付け足すだけです。