EF Core需模型定义、上下文配置和迁移机制协同工作;缺一不可,否则SaveChanges()静默失败或抛InvalidOperationException;DbContext必须显式配置连接字符串和数据库提供程序,否则报“No database provider has been configured”。

EF Core 不是“装完包就能查数据”的工具,它依赖明确的模型定义、上下文配置和迁移机制三者协同。跳过任一环节,SaveChanges() 会静默失败或抛出 InvalidOperationException,而不是报出你预期的数据库错误。
DbContext 必须显式配置连接字符串和提供器
只写 public class AppDbContext : DbContext 不够,EF Core 完全不知道连哪、用哪种数据库。没配 OnConfiguring 或没通过 DI 注入选项,运行时会直接报 No database provider has been configured。
- 本地开发快速验证可用
OnConfiguring:在重写方法里调用optionsBuilder.UseSqlite("Data Source=app.db")或UseSqlServer(...) - 生产环境必须用依赖注入 +
appsettings.json:避免硬编码,也方便切换环境(如开发用 SQLite,测试用 SQL Server) - SQLite 路径要注意权限:Windows 上写
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)比写死C:\temp更可靠
实体类不加主键或命名不合规,迁移会失败
EF Core 默认按约定找主键:属性名是 Id 或 BlogId(类名+Id),类型是 int、long 或 Guid。如果写成 public int blog_id { get; set; },它不会识别为主键,生成的迁移里就没有 PRIMARY KEY,dotnet ef migrations add 可能成功,但 dotnet ef database update 会卡在建表阶段。
- 强制指定主键用
[Key]特性,或 Fluent API 中写modelBuilder.Entity<Blog>().HasKey(e => e.BlogId) -
string类型默认映射为nvarchar(max),插入超长内容不报错但性能差;加[StringLength(200)]才会生成nvarchar(200) - EF Core 6+ 要求非空引用类型字段必须有默认值或标记为可空,否则编译过、运行时报
InvalidOperationException:“The property 'Name' cannot be marked as nullable”
Migration 命令不是“一键同步”,必须理解 Up/Down 的边界
dotnet ef migrations add 不是自动更新数据库,它只生成一个 C# 文件,里面是 Up(MigrationBuilder migrationBuilder) 和 Down(MigrationBuilder migrationBuilder)。真正改库的是 dotnet ef database update —— 它执行的是最后一次迁移的 Up 方法,不是所有迁移叠加执行。
- 删掉迁移文件夹再重新
add,不会清空已有数据库,只会让 EF Core 认为“当前模型就是初始状态”,后续update可能报错(比如已有表但没对应迁移记录) - 修改实体后忘了
add新迁移就直接update,EF Core 会静默跳过,数据库结构不变,但代码里新增字段读不到、写不进 - 想导出 SQL 脚本审核?用
dotnet ef migrations script --idempotent,别直接靠update猜效果
DbSet 查询不加 AsNoTracking() 容易拖慢列表页
默认情况下,EF Core 对每个查询结果都开启变更跟踪(Change Tracking)。这意味着:你查 1000 条 Blog,EF Core 就在内存里维护 1000 个实体快照。如果只是展示列表、不做修改,这纯属浪费内存和 CPU。
- 只读场景一律用
context.Blogs.AsNoTracking().ToList() - 关联查询(如查 Blog 带 Posts)记得用
.Include(b => b.Posts),否则会触发 N+1 查询——查 100 个 Blog,额外发 100 次 SQL 查 Posts -
FindAsync()内部走主键索引且自动AsNoTracking(仅限单条),比Where(x => x.Id == id).FirstOrDefaultAsync()略快,但语义不同:前者查不到返回 null,后者查不到也返回 null,区别在缓存行为
最常被忽略的一点:EF Core 的 DbContext 是**有生命周期的**。把它当静态单例用,或在 Web API 里手动 new 出来不 dispose,迟早遇到连接池耗尽、并发修改异常或内存泄漏。.NET 6+ 推荐注册为 Scoped,让它随 HTTP 请求自动创建和释放。


















