calx
@calx

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

指定したフィールドを更新したくない

解決したいこと

・レコードの更新の際、指定したフィールドだけ更新したくないです。

ASP.NET Core MVCの練習でWebアプリを作成しています。
EntityFramework Coreを使ったモデルクラスを定義し、スキャフォールディングでCRUD機能を自動生成してあります。
Controller内のEditメソッドで下記の通りだとすべて更新されてしまいますが、指定したフィールドを除く書き方を知っている方いましたら教えていただきたいです。

 _context.Update(article);
 await _context.SaveChangesAsync();

ASP.NET Coreを触り始めたばかりなので初歩的な質問失礼します。

1

1Answer

自己解決しました。指定したフィールド以外まとめて更新したかったのですが、更新したいものを一つ一つ書いていったら一応望んでいる結果にはなりました。

var articleToUpdate = await _context.Article.FindAsync(id);
articleToUpdate.Title = article.Title;
articleToUpdate.Content = article.Content;
~
await _context.SaveChangesAsync();
0Like

Comments

  1. 自己解決されたとのことですが、同じことで悩む方もいらっしゃると思うので解説させてください。

    上記のようにDbContextから取得したエンティティ(上記ではarticleToUpdate)は、「変更されていない」状態になっています。
    これは、次のコードで確認することができます。

    var article = await _context.Article.FindAsync(id);
    var state = _context.Entry(article).State; // state == EntityState.Unchanged
    

    もし、articleがDbContextから取得されたものではなく直接生成されたものである場合、StateEntityState.Detachedになっています。

    var article = new Article { Id = xxx, Title = "xxx", Content = "xxx" };
    var state = _context.Entry(article).State; // state == EntityState.Detached
    

    これらの状態でSaveChanges()を呼び出しても、変更を検出できないので、UPDATE文は実行されません。

    ここで、Titleプロパティに新しい値を設定します。すると、StateがModified(変更済)に変わります。

    var article = await _context.Article.FindAsync(id);
    article.Title = "new title";
    var state = _context.Entry(article).State; // state == EntityState.Modified
    

    同時に、Titleプロパティのプロパティ情報も、変更済としてマークされます。変更されていないContentプロパティは、未変更のままです。

    var article = await _context.Article.FindAsync(id);
    article.Title = "new title";
    var state = _context.Entry(article).State; // state == EntityState.Modified
    var isTitleModified = _context.Entry(article).Property(e => e.Title).IsModified; // true
    var isContentModified = _context.Entry(article).Property(e => e.Content).IsModified; // false
    

    SaveChanges()は、上記の状態に基づいてTITLEのみをUPDATEするSQLを発行します。

    これに対して_context.Update(article)は、全てのプロパティを「変更済」としてマークします。

    var article = await _context.Article.FindAsync(id);
    article.Title = "new title";
    _context.Update(article);
    var state = _context.Entry(article).State; // state == EntityState.Modified
    var isTitleModified = _context.Entry(article).Property(e => e.Title).IsModified; // true
    var isContentModified = _context.Entry(article).Property(e => e.Content).IsModified; // true
    

    Entity Framework(Core)について良くわかっていない人は、更新したいエンティティについて無条件にこのUpdateメソッドを呼び出しがちなのですが、ここまで読んだ方にはお分かりのように、本来、適切にエンティティのプロパティを設定したのであれば、Updateメソッドの呼び出しは不要です。Updateメソッドを呼び出してしまうと、本来不要なフィールドまで更新するUPDATE文が発行されてしまうからです。

    もし、ORマッパーなどが自動的に全てのプロパティを設定してしまう為に全てのプロパティのIsModifiedtrueになってしまう場合には、次のように、対象外としたいプロパティのみ、変更済のマークを外すことができます。

    var article = await _context.Article.FindAsync(id);
    
    // 全ての入力値をエンティティに反映
    MapModelToEntity(model, article);
    
    // Contentプロパティは更新対象から外す
    _context.Entry(article).Property(e => e.Content).IsModified = false;
    
    var state = _context.Entry(article).State; // state == EntityState.Modified
    var isTitleModified = _context.Entry(article).Property(e => e.Title).IsModified; // true
    var isContentModified = _context.Entry(article).Property(e => e.Content).IsModified; // false
    

    詳しくはこちらの記事などが参考になるかもしれません。

    また、これも同様に勘違いされやすい点なのですが、更新対象のレコードのキーを既に知っている場合には、DbContextから取得する必要もありません。

    var article = new Article { Id = 123, Title = "New Title" };
    
    // エンティティを追跡状態(変更済)にする
    _context.Entry(article).State = EntityState.Modified;
    
    // Contentプロパティは更新対象から外す
    _context.Entry(article).Property(e => e.Content).IsModified = false;
    
    // 更新
    _context.SaveChanges();	// Content以外のプロパティを更新するUPDATE文が実行される
    

    もちろん、Idが正しくない場合には例外が発生しますので、ご注意ください。

  2. @calx

    Questioner

    大変丁寧な解説ありがとうございます。

Your answer might help someone💌