EF Core 支持三种安全高效的部分更新方式:一是 EF Core 7+ 的 ExecuteUpdate,直接生成 SQL 不加载实体;二是 DbContext.Entry 配合 IsModified 手动标记字段;三是基于 DTO 的 Patch 更新,兼顾安全性与灵活性。
EF Core 本身不直接支持“只更新指定字段”的原生 Partial Update(不像 Dapper 可以手写 SQL),但有几种安全、高效且符合 EF Core 设计理念的实现方式。核心原则是:避免先查再改(Select-Update)带来的性能和并发风险,优先用 无查询更新(No-Query Update) 或 正确标记变更状态。
这是最干净、最高效的部分更新方式,直接生成 SQL UPDATE 语句,不加载实体到内存,不触发跟踪,无 N+1 风险。

Where 条件确保目标行唯一示例:
context.Blogs
.Where(b => b.Id == blogId)
.ExecuteUpdate(b => b.SetProperty(x => x.Title, "新标题")
.SetProperty(x => x.LastModified, DateTime.UtcNow));
适合需要基于已有实体对象做选择性更新,且你明确知道哪些字段变了(比如从 API 接收 Patch DTO 后映射)。
示例:
var blog = new Blog { Id = blogId, Title = "新标题", LastModified = DateTime.UtcNow };
context.Blogs.Attach(blog);
context.Entry(blog).Property(x => x.Title).IsModified = true;
context.Entry(blog).Property(x => x.LastModified).IsModified = true;
await context.SaveChangesAsync();
对接口友好、防篡改、可验证,适合 RESTful PATCH 场景。
BlogPatchDto),只包含允许修改的字段AsNoTracking() 提升读取性能)Update 后,遍历 DTO 中非 null/非默认值字段,设为 Modified关键点:避免对未传字段(如 null 的字符串、0 的 int)误覆盖原值 —— 所以判断逻辑要结合 DTO 是否“有意提供”该字段(可用 JsonSerializerOptions.IncludeFields = true 或自定义标记)。
以下代码看似简单,实则危险:
var blog = await context.Blogs.FindAsync(id); // 加载全部字段 blog.Title = "新标题"; // 其他字段保持原值?不一定! await context.SaveChangesAsync(); // EF 会把所有 tracked 字段都写入 UPDATE
问题:
– 若实体有大量字段(如 20+ 列),却只改 1 个,浪费带宽与日志空间
– 若并发修改,可能意外覆盖他人刚更新的字段(丢失更新)
– 无法区分“字段没传”和“字段传了 null 想清空”
基本上就这些。用 ExecuteUpdate 最快最省心;用 Entry + IsModified 更灵活可控;DTO 方案最健壮。选哪个,看你的数据敏感度、性能要求和 API 设计风格。