【ASP.NET Core】让 EF Core 生成更“合理”的 UPDATE 语句
相信 .NET 玩家都知道,EF Core 自动产生 SQL 更新语句是通过跟踪实体状态实现的。不过,在 Web 中是无状态的,查询出一个东东返回到前台,然后用户要更新数据,POST 过来就是修改后的数据。
比较笨的方法是先根据主键查询出数据,然后把客户端 POST 过来的数据赋值,这样就有新旧值的比较了,EF Core 也能生成准确的 SET 子句了。这种方法虽然笨但很简单,它有个缺点:在更新前你会多跑一轮查询。
本文咱们就用这个实体来演示。
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 一遍。其实,这种把所有字段都 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"); }
以前老周告诉过大伙伴:EF Core 从实体查询出来的时候开始跟踪。那是指正常情况,不正常情况下可以调用一下 Attach 方法。这个方法的功能就是把一个实体实例手动添加到 EF Core 的跟踪列表中,并且将实体状态设置为 Unchanged,即未更改。
随后,调用 Update 方法,会将实体状态变为 Modified,当然,EF Core 仍然不知道哪些属性被修改了,所以,除主键以外,其他属性都变为 Modified。


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>
在 form 中,咱们只要用一个隐藏字段保存旧的数据(JSON)就行,然后在接收 POST 的 action 方法中加上名为 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 这句,就是把原来备份的数据作为快照,它就能跟新的值比较了。
再来看看效果。

现在,咱们只改变了学生的专业名称。然后提交。

看看 EF Core 生成的 SQL 语句。
UPDATE "tb_students" SET "Major" = @p0 WHERE "Id" = @p1 RETURNING 1;
由于现在它能识别到只有 Major 属性的值改变了,所以 SET 子句只更新这个属性。
这样解决是不是很舒服?不会丧失 EF Core 自动生成 SQL 的功能,又不用自己拼装 SQL 语句。
这次分享的内容比较简单,所以就到这里了。至于大伙伴可能疑惑老周又跑去干什么大项目了,这么久没写文章了。想多了,大项目不会找老周这种乡下码农的,人家肯定找那些满屋子放满证书的靓仔。
最近是搞了个 Stm32 的小项目,也没多复杂,只用了 9 天时间。主要是前期去他们工厂跟他们吵了四五天,最后才确定方案。而且 Boss 还想用 500 块钱打发的态度,还没脑子的瞎指挥(这货就是啥也不懂还乐于装 X 那种)。还是人家厂里的经理懂事,项目能搞成这小伙子的功劳不小,两边讲道理。老周一开始就给 boss 放下狠话的,你要是几百块能打发的你自己用 AI 做,不必去找人来干;只要你找到我给做,我可以按你们的需求做,但你得按我的思路来,不要在那里瞎叫喝。你去打听打听,我当年刚大学毕业,面试就敢硬刚 HR,把 HR 怼到辞职的。你,还排不上号。
原文地址: https://www.cveoy.top/t/topic/qHRA 著作权归作者所有。请勿转载和采集!