DbContext 必须依赖注入,禁用手动 new;Database First 适用于已有数据库,Code First 适用于新项目主导设计;SaveChangesAsync 需确保实体被跟踪;迁移应生成 SQL 脚本人工审核后执行。

DbContext 直接 new 还是依赖注入?
绝大多数新项目里,DbContext 必须走依赖注入,不能手动 new。手动实例化会导致连接未释放、上下文状态混乱、并发时数据错乱——尤其在 Web API 或 ASP.NET Core 中,一个请求多个地方调用 new DbContext(),很容易触发 InvalidOperationException: A second operation started on this context before a previous operation completed。
实操建议:
- 在
Program.cs中调用services.AddDbContext<appdbcontext>()</appdbcontext>,并指定生命周期(Web 场景用ServiceLifetime.Scoped) - 控制器或服务类中通过构造函数接收
AppDbContext,别自己new - 如果真要临时用(比如命令行工具或单元测试),用
using var ctx = new AppDbContext(...)确保及时释放
Database First、Code First、Model First 到底选哪个?
Model First 已被微软弃用,VS 2022 起完全不支持;Database First 和 Code First 是唯二可行路径,选法取决于团队控制权和数据库演化节奏。
常见错误现象:刚建好项目就跑 Scaffold-DbContext 生成一堆实体,结果后续改表不敢动,因为怕覆盖代码;或者反向写完 DbSet<user></user> 又不敢执行 dotnet ef migrations add Init,怕炸掉生产库。
实操建议:
- 已有稳定数据库 → 用 Database First:
Scaffold-DbContext "Server=..." Microsoft.EntityFrameworkCore.SqlServer -o Models -f,加-f覆盖已有文件,但先git commit再操作 - 新项目且能主导数据库设计 → 用 Code First:定义
User类 → 配置OnModelCreating→dotnet ef migrations add Init→dotnet ef database update - 别混用:别一边 Scaffold 一边手写 Migration,EF 会无法追踪模型变更
SaveChangesAsync() 为什么有时不生效?
不是所有修改都会被保存——EF 只跟踪“已知实体”的变化,对直接拼 SQL、原生 ADO.NET 查询、或脱离上下文作用域的对象修改完全无感。
典型场景:从 API 接收 JSON 得到 userDto,然后 ctx.Users.Update(userDto),但没设主键或没查过该记录,EF 会以 Added 状态插入而非更新;又或者用了 AsNoTracking() 查询后直接改属性,SaveChanges 时 EF 根本不知道这个对象曾被加载过。
实操建议:
- 更新前确保实体被 EF 跟踪:要么用
ctx.Users.Find(id)查出来再改,要么用ctx.Entry(entity).State = EntityState.Modified - 批量更新别依赖
SaveChanges:1000 条记录逐条改再保存,性能差还容易超时;改用ExecuteSqlRaw或第三方库如Z.EntityFramework.Extensions - 检查返回值:
int affected = await ctx.SaveChangesAsync(),如果返回 0,说明没检测到变更,不是“成功但没改”
迁移(Migration)在多环境怎么安全落地?
本地跑 dotnet ef migrations add 没问题,但上线时直接 dotnet ef database update 是高危操作——它会自动执行所有待处理迁移,包括你忘了测试的、依赖特定配置的、甚至删表的迁移。
容易踩的坑:CI/CD 流水线里用 dotnet ef database update 自动更新生产库,结果某次迁移脚本有 bug,把用户表字段类型从 nvarchar(50) 改成 int,服务直接崩;或者开发机上生成的迁移含 Windows 路径,部署到 Linux 容器就报错。
实操建议:
- 永远生成 SQL 脚本而不是自动执行:
dotnet ef migrations script --idempotent --output deploy.sql,人工审核后再在目标库运行 - 不同环境用不同连接字符串:开发用 LocalDB,测试用 Docker SQL Server,生产用 Azure SQL,迁移脚本必须对应环境验证
- 迁移类名别带时间戳以外的信息:避免
20240501120000_AddUserEmail_Index这种命名,EF 不认下划线逻辑,只按前缀数字排序
迁移不是“点一下就完事”的功能,它是数据库 schema 的版本控制,和代码提交一样需要评审、测试、回滚预案。少有人记得给迁移加 Sql("CREATE INDEX ...") 后手动补 Down(MigrationBuilder) 方法,结果回滚时报错,这事儿真发生过。


















