ef core 增删改查易错点有三:漏调 savechangesasync()、上下文生命周期管理不当(禁用 singleton/静态,web 用 scoped、脚本用 using)、查询过早 tolist()。 dbcontext 非线程安全,须按场景正确释放;update() 全量更新风险高,推荐局部标记;异步方法必须 await,避免混用同步/异步调用。

EF Core 的增删改查本身不难,难的是漏掉 SaveChangesAsync()、用错上下文生命周期、或在查询时过早触发 ToList() —— 这三处出错,90% 的“数据没写入”“查得慢”“并发报错”就都来了。
DbContext 必须按场景管好生命周期,不能 new 完就扔着不管
EF Core 的 DbContext 不是线程安全的,内部持有连接、变更追踪器和缓存。长期复用一个实例,轻则内存泄漏,重则抛 InvalidOperationException: A second operation started on this context before a previous operation completed。
- ASP.NET Core Web 应用:必须注册为
Scoped,通过构造函数注入 —— 每个 HTTP 请求独享一个实例 - 控制台或短期脚本:用
using包裹,确保Dispose()被调用,例如:using var context = new AppDbContext(options);<br>await context.Users.AddAsync(new User { Name = "Alice" });<br>await context.SaveChangesAsync(); - 绝对不要:把
DbContext存在静态字段里,或注册成Singleton服务
Add/Remove/Update 后必须显式调用 SaveChangesAsync() 才写库
所有 Add()、Remove()、Update() 都只是标记实体状态,不会发 SQL。漏掉 SaveChangesAsync(),等于什么都没做。
-
Add():适合新实体(ID 为默认值),状态设为Added -
Remove():传已跟踪实体可直接删;传未跟踪实体(如只带 ID 的对象),需先Attach()再Remove() -
Update():慎用!它会把整个实体标记为Modified,导致所有字段被 UPDATE —— 即使你只改了一个属性 - 更安全的局部更新:
context.Entry(user).Property(e => e.Email).IsModified = true;
IQueryable 查询别过早 ToList(),否则全表拉进内存
IQueryable<t></t> 是延迟执行的表达式树。ToList()、ToArray()、foreach 枚举会立刻执行 SQL 并加载全部结果。大数据量下,这等于放弃数据库索引和分页能力。
- 错误写法:
context.Orders.ToList().Where(o => o.Status == "Shipped")→ 全表查出再内存过滤 - 正确写法:
await context.Orders.Where(o => o.Status == "Shipped").ToListAsync() - 分页必须在服务端完成:
context.Orders.Skip(20).Take(10),不是.ToList().Skip().Take() - 统计总数用
CountAsync(),不是ToList().Count - 不确定是否服务端执行?加日志:
options.LogTo(Console.WriteLine),或用ToQueryString()看生成的 SQL
异步方法必须 await,且不能混用同步/异步调用链
EF Core 的异步方法(如 FindAsync()、ToListAsync()、SaveChangesAsync())不是“锦上添花”,而是 Web 场景下的刚需。不 await,线程可能提前释放,导致上下文被回收或状态错乱。
- 所有 async 方法必须配
await,不能只写context.SaveChangesAsync()(缺await) - 控制器 Action 必须声明
async Task<iactionresult></iactionresult>,不能返回Task或void - 避免混用:比如
await context.Users.FindAsync(id)后,又用context.SaveChanges()(同步版)—— 事务上下文可能已失效 - 批量插入优先用
AddRange()+ 单次SaveChangesAsync(),别循环调Add()+SaveChangesAsync()
最常被忽略的其实是“查询前没加 Where 就先 ToList()”,或者“以为 Add() 就等于插入成功”。这些点不盯紧,调试时翻日志都找不到问题在哪。











