ef core需显式管理dbcontext生命周期、调用savechangesasync()提交变更、确保查询条件下推至sql层,否则将导致数据未写入、性能低下或并发错误。

EF Core 不是“写完代码就自动同步数据库”的魔法工具,它需要你明确控制上下文生命周期、显式提交变更、并在查询时让条件落在 SQL 层——漏掉任何一环,数据就写不进库、查得慢、或并发报错。
DbContext 必须用依赖注入或 using,不能 new 完扔着不管
DbContext 内部持有数据库连接、变更追踪器和快照缓存,不是线程安全的。手动 new DbContext() 后不释放,轻则连接池耗尽,重则触发 InvalidOperationException: A second operation started on this context before a previous operation completed。
- ASP.NET Core Web 项目:在
Program.cs中调用services.AddDbContext<appdbcontext>(ServiceLifetime.Scoped)</appdbcontext>,控制器里通过构造函数接收,别自己 new - 控制台/脚本场景:必须用
using var ctx = new AppDbContext(options);包裹操作,确保Dispose()被调用 - 绝对禁止:静态字段存
DbContext、注册为Singleton、或在循环里反复 new
SaveChangesAsync() 不是可选步骤,而是强制开关
Add()、Update()、Remove() 都只是修改 EF 内部状态标记,不会发任何 SQL。没调 SaveChangesAsync(),等于什么都没做——这是“数据没写入”最常见原因。
- 所有增删改操作后,必须显式 await
context.SaveChangesAsync() -
Update()是全量更新:哪怕你只改了Email,生成的 SQL 也会 SET 所有列,含 NULL 覆盖未赋值字段 - 更安全的局部更新写法:
context.Entry(user).Property(e => e.Email).IsModified = true;
IQueryable 查询别过早 ToList(),否则全表拉进内存
IQueryable<t></t> 是表达式树,真正执行 SQL 在枚举或调用 ToList() 时。过早 ToList() 会把整张表加载进内存再过滤,失去数据库索引、分页、聚合能力。
- 错误:
context.Users.ToList().Where(u => u.Status == "Active")→ 全表查出再内存遍历 - 正确:
await context.Users.Where(u => u.Status == "Active").ToListAsync()→ WHERE 下推到 SQL - 分页必须服务端做:
context.Users.Skip(100).Take(20),不是.ToList().Skip().Take() - 总数统计用
CountAsync(),不是.ToList().Count
Code First 迁移要人工审核 SQL,别直接 dotnet ef database update
迁移脚本(dotnet ef migrations add Init)生成的是 C# 类,最终执行靠 dotnet ef database update 转成 SQL。但 EF 生成的 SQL 不一定适合生产环境——比如加唯一索引可能锁表、删除列会丢数据、外键约束顺序错乱。
- 生成迁移后,先看
Migrations/xxx_Init.cs里的Up(MigrationBuilder migrationBuilder) - 用
dotnet ef migrations script -o migration.sql导出 SQL,人工检查锁表、数据丢失、约束冲突风险 - 生产环境严禁直接
database update;应把 SQL 交给 DBA 审核后,在维护窗口执行 - Database First 场景下,慎用
-f参数覆盖实体类,先git commit再 Scaffold
最常被忽略的其实是“查询条件是否真在数据库执行”——EF 不报错,但 .Where() 写在 .ToList() 后面,就等于放弃所有数据库能力。调试时打开日志(options.LogTo(Console.WriteLine)),一眼就能看出 SQL 是不是按你预期生成的。










