.NET プレイヤーは皆、EF Core がエンティティのステータスを追跡することによって SQL 更新ステートメントを自動的に生成することを知っていると思います。ただし、Web はステートレスです。何か問い合わせた後、フロントデスクに返却されます。次に、ユーザーはデータを更新したいと考えており、POST は変更されたデータです。
より愚かな方法は、最初に主キーに基づいてデータをクエリし、次にクライアントによって POST されたデータを割り当てることです。これにより、古い値と新しい値を比較でき、EF Core は正確な SET 句も生成できます。この方法は愚かですが簡単です。これには欠点があり、更新する前にもう 1 つのクエリを実行する必要があります。
この記事では、このエンティティを使用して説明します。
public class Student { public int Id { get; set; } public string Name { get; set; } = default!; public string? Major { get; set; } }
public class DemoDbContext : DbContext { public DemoDbContext(DbContextOptionsopt) : base(opt) { } public DbSet Students { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity (ent => { ent.ToTable("tb_students"); // 初始数据 ent.HasData(new Student { Id = 1, Name = "吴小车", Major = "挖矿" }, new Student { Id = 2, Name = "李肺", Major = "攻城掠地" }, new Student { Id = 3, Name = "赵逗逗", Major = "打磨芯片" }, new Student { Id = 4, Name = "范统", Major = "悲观经济学" }, new Student { Id = 5, Name = "贾仁亿", Major = "社会猪圈学" }); }); } }
Major 属性は、学生が選択した専攻を示します。
このとき、公式ドキュメントで紹介されている ExecuteUpdate メソッドを思い浮かべると思います。クエリは必要なく、UPDATE ステートメントを直接送信できます。
_ = await dbcontext.Students.ExecuteUpdateAsync(setter => { setter.SetProperty(stu => stu.Name, newStu.Name); setter.SetProperty(stu => stu.Major, newStu.Major); });
上記の方法ではクエリを回避できますが、古い値と新しい値の比較は行われません。ユーザーがどの属性を変更したかわからないため、すべての属性を SET できるのは 1 回だけです。実際、すべてのフィールドを SET するこのメソッドはより単純な操作です。エンティティのステータスを Modified に変更するだけです。 EF Core は完全な SET 句も生成します。
public async TaskUpdateStudent(Student stu/*, string old*/) { var dbcontext = HttpContext.RequestServices.GetRequiredService (); // 开始跟踪 dbcontext.Students.Attach(stu); // 强行改变实体状态 dbcontext.Students.Update(stu); // 提交更新 _ = await dbcontext.SaveChangesAsync(); return RedirectToAction("Index"); }
Lao Zhou 氏はかつて自分の主要パートナーに、「エンティティがクエリされると EF Core は追跡を開始します」と語ったことがあります。それは通常の状態を指します。異常な状況では、Attach メソッドを呼び出すことができます。このメソッドの機能は、EF Core の追跡リストにエンティティ インスタンスを手動で追加し、エンティティのステータスを Unchanged、つまり未変更に設定することです。
その後、Update メソッドを呼び出すと、エンティティのステータスが Modified に変更されます。もちろん、EF Core はどの属性が変更されたかをまだ認識していないため、主キーを除いて、他のすべての属性が変更されました。


EF Core によって生成される SQL ステートメントは次のとおりです。
UPDATE "tb_students" SET "Major" = @p0, "Name" = @p1 WHERE "Id" = @p2 RETURNING 1;
Name 属性は変更していませんが、EF Core では比較判定できないため、Name 属性も設定されます。
それでは、どのプロパティが変更されたかを EF Core に知らせる方法はあるのでしょうか?はい、EF Core ではエンティティの状態を変更できるだけでなく、古い値 (スナップショット値) と新しい値 (現在の値) を手動で設定することもできます。ただし、HTTP ステートレスの問題、つまり HTTP は一連のリクエスト後に痕跡を残さないという問題を解決する必要があります。したがって、ユーザーがデータを要求すると、クライアントに戻るときにエンティティ オブジェクトのバックアップが作成されます。ユーザーが編集を完了すると、バックアップの古い値とともにサーバーに POST されて戻され、比較できるようになります。
@model DB.Student <form class="modal-dialog" method="post" asp-action="@nameof(HomeController.UpdateStudent)"> <input type="hidden" asp-for="Id" /> @{ // 把旧的值序列为 JSON string json = JsonSerializer.Serialize(Model); } <input type="hidden" name="old" value="@json"/> <div class="modal-content"> <div class="modal-header"> <h5>编辑学生信息h5> <button type="button" class="btn-close" data-bs-dismiss="modal">button> div> <div class="modal-body"> <div class="row mb-3"> <label class="form-label" asp-for="Name">姓名:label> <input type="text" class="form-control" asp-for="Name" /> div> <div class="row mb-3"> <label class="form-label" asp-for="Major">专业:label> <input class="form-control" type="text" asp-for="Major" /> div> div> <div class="modal-footer"> <button type="button" data-bs-dismiss="modal" class="btn btn-secondary">取消button> <button type="submit" class="btn btn-primary">确定button> div> div> form>
フォームでは、隠しフィールドを使用して古いデータ (JSON) を保存し、POST を受け取るアクション メソッドに old という名前の文字列パラメータを追加するだけです。
public async TaskUpdateStudent(Student stu, string old) { var dbcontext = HttpContext.RequestServices.GetRequiredService (); // 开始跟踪 dbcontext.Students.Attach(stu); var entry = dbcontext.Students.Entry(stu); // 反序列化 var oldvals = JsonSerializer.Deserialize (old); // 重点:设置快照 entry.OriginalValues.SetValues(oldvals!); // 提交更新 _ = await dbcontext.SaveChangesAsync(); return RedirectToAction("Index"); }
ここで最も重要なことは、OriginalValues.SetValues という文です。これは、元のバックアップ データをスナップショットとして取得し、それを新しい値と比較します。
上記のコードは実際には簡略化できます。 Attach メソッド自体は Entry を返すため、
// 开始跟踪 var entry = dbcontext.Students.Attach(stu); // 反序列化 var oldvals = JsonSerializer.Deserialize(old); // 重点:设置快照 entry.OriginalValues.SetValues(oldvals!); ……
効果を見てみましょう。

ここでは、学生の専攻名のみを変更しました。その後、提出してください。

EF Core によって生成された SQL ステートメントを見てみましょう。
UPDATE "tb_students" SET "Major" = @p0 WHERE "Id" = @p1 RETURNING 1;
Major 属性の値のみが変更されたことが認識されるため、SET 句はこの属性のみを更新します。
これは快適な解決策ではないでしょうか? SQL を自動的に生成する EF Core の機能が失われることはなく、SQL ステートメントを自分でアセンブルする必要もありません。
今回シェアする内容は比較的シンプルなのでここまでです。大きな友人たちにとっては、Lao Zhou が長い間記事を書いていないため、再びどのような大きなプロジェクトに取り組むことになったのか疑問に思っているかもしれません。考えすぎると、Lao Zhou のような田舎のプログラマーを大きなプロジェクトに求めることはなくなるでしょう。彼らは間違いなく証明書でいっぱいの部屋を持ったハンサムな男性を探しているでしょう。
最近、私は小規模な Stm32 プロジェクトに取り組みましたが、それほど複雑ではなく、9 日しかかかりませんでした。一番の理由は、私が彼らの工場に行き、最終的に計画をまとめるまで4、5日間彼らと議論したことです。しかもボスは未だに500元使って人を殺したいと思っていて、頭も分からず盲目的な指示を出してくる(この男は何も理解せずXのふりをして喜んでいる)。良識あるのは工場のマネージャー (おそらくマネージャー) であり、プロジェクトを成功させたこの若者は大いに評価されるに値するので、私たちはお互いに議論することができます。ラオ・イーイーは最初から上司に厳しい言葉をかけた。数百ドルを使えるのであれば、AI を使用して自分で行うことができます。それをやってくれる人を見つける必要はありません。あなたが私にやってくれるとわかってくれれば、私はあなたのニーズに応じてやりますが、あなたは私の考えに従わなければならず、大声で怒鳴ったりしないでください。行って聞いてみましょう。大学を卒業したばかりの頃、面接で人事にあえて厳しい態度をとったことが激怒して退職しました。あなたはまだランク付けされていません。