EF Core 的 Remove 方法标记实体为待删除,实际删除在 SaveChanges 时执行;支持断开模式(仅主键)和追踪模式(先查询后删),批量删用 RemoveRange,大表应避免;级联行为由 OnDelete 配置。

EF Core 的 Remove 方法用于标记一个实体为“待删除”,真正执行删除操作要等到调用 SaveChanges() 或 SaveChangesAsync() 时才发生。它不是直接发 SQL 删除语句,而是走变更追踪流程——所以用法和注意事项比看起来更关键。
删除单个已知主键的实体(断开模式)
适用于你**只知道 ID、没从数据库查过该实体**的场景。比如前端传了个 ID 要删,你不想先查再删(避免两次数据库往返):
- 手动构造一个只带主键值的实体实例,传给
context.Remove() - EF Core 会尝试按主键去数据库删对应行;如果该主键在表中不存在,
SaveChanges()会抛出DbUpdateConcurrencyException,提示“预期影响 1 行,实际影响 0 行” - 示例:
var student = new Student { StudentId = 100 };
context.Remove(student);
await context.SaveChangesAsync();
删除已查询出来的实体(追踪模式)
这是最安全、最推荐的方式:先用 Find()、FirstOrDefault() 等方法从数据库加载实体,再删:
- 实体处于
Unchanged状态,Remove()会将其状态改为Deleted - 即使数据在你查询后被别人删了,
SaveChanges()仍会报并发异常,但你能明确知道是“查到了但已被删”,而不是“根本不存在” - 示例:
var dept = await context.Departments.FindAsync(5);
if (dept != null)
{
context.Remove(dept);
await context.SaveChangesAsync();
}
批量删除多个实体
用 RemoveRange() 一次删多个,支持传入集合或多个参数:
- 可以传
List、数组,甚至两个实体(如RemoveRange(e1, e2)) - 同样遵循“断开删需确保主键存在,追踪删更安全”的原则
- 注意:它仍是逐条生成 DELETE 语句,**不适合清空大表**;大数据量建议用原生 SQL
TRUNCATE或DELETE FROM - 示例:
var toDelete = context.Students.Where(s => s.Status == "Inactive").ToList();
context.RemoveRange(toDelete);
await context.SaveChangesAsync();
级联删除与外键行为
如果实体有关联子项(如 Department 有多个 Employee),删父实体时子项是否自动删,取决于你在 OnModelCreating 中配置的 OnDelete() 行为:
-
DeleteBehavior.Cascade:删 Department 同时删所有关联 Employee -
DeleteBehavior.SetNull:Employee.DepartmentId 设为 NULL(要求字段可空) -
DeleteBehavior.Restrict:禁止删,除非先清空子项 - 配置后必须重新运行
dotnet ef migrations add和update
基本上就这些。核心就两点:删之前尽量确认实体存在,大表别用 RemoveRange 硬扛。










