拓冰建站拓冰建站
首页 / 资讯中心 / 正文

EF Core Code First建库避坑指南:从实体到迁移的完整链路

1. 为什么我坚持用 Code First 建库一次被 Database First 逼疯的经历C# 用 EF Code First 方式创建数据库这几年几乎是我所有新项目的默认选择。最早我其实是 Database First 的忠实用户觉得“先在 SSMS 里把表建好再让 VS 反向生成实体”特别省事。直到后来被现实反复捶打才彻底转向 Code First。这篇文章不是单纯教命令而是把一个新项目从空解决方案到数据库真正建好、再到后续改表上线的完整过程讲清楚顺带把我在这个过程中踩过的坑都摊开。想学 EF Core 的新人可以从头跟着做已经在用 EF Core 但没有系统梳理过建库流程的人也能对照检查自己的习惯。1.1 Code First 和 Database First / Model First 的本质区别Code First 的核心思想是代码即表结构真源。你写的实体类决定数据库的表DbContext里的映射配置决定字段、索引、关系迁移文件负责记录每一次结构变化。数据库本身变成了一个“可以由代码重建”的产物而不是必须要人肉维护的独立系统。而 Database First 正好反过来数据库是真源实体类由数据库反向生成。早期做老系统改造时很多项目都是先在 SQL Server 里建好表再用 ADO.NET Entity Data Model 把表拖进模型。看起来快了但一旦业务迭代起来就很痛苦DBA 加一个字段、改一个索引你这边要重新生成模型文件模型文件一多生成时经常产生大量 diff 干扰最后根本不知道哪些改动是人为的哪些是生成器自己的。还有 Model First 这种老古董是当年用 EDMX 设计器在界面上画实体关系再由模型生成数据库。在 EF Core 时代已经基本被淘汰了我这里不展开。方式谁是真源改结构时谁先动适合场景Database First数据库先改库再刷新模型存量老库、DBA 主导的项目Model First设计器里的模型先改模型再生成库基本只出现在旧 EF 项目里Code First实体类与映射配置先改代码再生成迁移新项目、快速迭代、持续交付团队你可能会问数据库真源有什么不好如果数据库里有一堆视图、存储过程、触发器和复杂的权限控制那 Database First 其实很合理。但是新项目往往没有这些历史包袱业务规则全在代码里这时候让数据库反过来控制器代码就是本末倒置。1.2 什么样子的项目适合 Code First什么样子的别硬上先说适合的。新项目、绿地系统、微服务架构里的单库服务这些场景下 Code First 几乎是最优解。团队可以把表结构变更当作代码变更来评审PR 里直接看到“这次加了一个CreatedBy字段顺带加了个索引”比 DBA 在测试环境手工改库要透明得多。本地开发也舒服。同一份代码任何一个新人 clone 下来跑一下迁移本地数据库就出来了不用拿着几十页 SQL 脚本去执行。CI 里也可以直接跑迁移生成数据库自动化测试的环境准备成本大幅下降。不适合的场景也很明确已经有庞大老库的系统不要拿 Code First 去硬接。老库里往往有大量历史遗留的表结构、诡异的字段类型、复杂存储过程用 Database First 反向生成反而更省事。另一种不适合的情况是“DBA 强管控 生产库禁止自动变更”的传统企业团队很难接受由应用代码直接改生产库结构这时候 Code First 的优势发挥不出来反而还多了一层沟通成本。所以我的建议是Code First 是工具不是信仰。判断标准就一条——这个系统里结构演进的主动权到底应该放在代码里还是放在数据库里。1.3 EF Core 和 EF6 的 Code First 差别标题里写“EF codefirst”很多人可能还在用老 EF6。EF6 的 Code First 和 EF Core 有一个非常重要的区别EF6 里有一套“数据库初始化策略”比如CreateDatabaseIfNotExists、DropCreateDatabaseIfModelChanges、DropCreateDatabaseAlways之类。这套东西在开发期很方便但在生产环境是灾难动不动就重建库或者因为模型和库对不上报错。EF Core 把这些自动初始化策略砍掉了只保留了两个明确的方法Database.EnsureCreated()和Database.Migrate()。前者适合临时项目后者适合正式系统。EF Core 跨平台、支持依赖注入、默认表名单数化、没有 EDMX 文件整体思路比 EF6 清爽很多。这篇文章里的命令和代码我都以当前主流的 EF Core 8.0 为准但我也会在相关位置提一下 EF6 的差异免得有人照着老教程踩坑。2. 从空项目到数据库落地一套能跑的完整链路与其只讲理论不如直接走一遍真实项目。下面这套流程我几乎每个新服务都在用包括创建解决方案、分离 Data 层、装包、写实体、配连接串、第一次建库。2.1 装包和工具少了 Design 包等于白干先建解决方案和两个项目一个放 EF 相关的类库一个放 Web API 启动项目。这种分离看着多一步但后续迁移文件会集中在 Data 项目里不会被 Web 项目里的其他代码污染。dotnet new sln -n BlogDemo dotnet new classlib -n BlogDemo.Data dotnet new webapi -n BlogDemo.Api dotnet sln add BlogDemo.Data BlogDemo.Api dotnet add BlogDemo.Api reference BlogDemo.Data然后给 Data 项目装 SQL Server 的 EF Core 提供程序。注意后面 Web 项目也要引用 Data 项目但真正和数据库打交道的程序集是放在 Data 里的所以这个包要装到 Data 项目。dotnet add BlogDemo.Data package Microsoft.EntityFrameworkCore.SqlServer接下来是最容易翻车的一步执行迁移命令必须在“启动项目”里能找到 EF Core 的设计时包。很多人装完 SqlServer 包就急着敲dotnet ef结果报“你的启动项目没有引用 Microsoft.EntityFrameworkCore.Design”。所以在 Web 项目里再装一个 Design 包dotnet add BlogDemo.Api package Microsoft.EntityFrameworkCore.Design最后安装全局的 dotnet-ef 工具这个只装一次机器上有就不用重复执行dotnet tool install --global dotnet-ef如果你用的是 Visual Studio 的包管理器控制台则需要装Microsoft.EntityFrameworkCore.Tools这个包然后就能用Add-Migration、Update-Database这组 PowerShell 命令。我个人更习惯 dotnet CLI因为它在 CI 和 Mac 上都能用。2.2 实体类与 DbContext表结构从哪来我这里的示例模型是一个标准的三层博客结构博客、文章、评论。先定义实体类注意这些代码不是随便写的每个属性都会映射到表里的列。public class Blog { public int Id { get; set; } public string Name { get; set; } string.Empty; public string? Description { get; set; } public DateTime CreatedAt { get; set; } public ICollectionPost Posts { get; set; } new ListPost(); } public class Post { public int Id { get; set; } public string Title { get; set; } string.Empty; public string? Content { get; set; } public int BlogId { get; set; } public Blog? Blog { get; set; } public ICollectionComment Comments { get; set; } new ListComment(); } public class Comment { public int Id { get; set; } public string Text { get; set; } string.Empty; public int PostId { get; set; } public Post? Post { get; set; } }然后是 DbContext。AppDbContext继承了 EF Core 的DbContext构造函数接收DbContextOptionsAppDbContext这就是依赖注入的标准接法。下面的DbSetT属性决定了 EF 会为哪些实体建表。public class AppDbContext : DbContext { public AppDbContext(DbContextOptionsAppDbContext options) : base(options) { } public DbSetBlog Blogs SetBlog(); public DbSetPost Posts SetPost(); public DbSetComment Comments SetComment(); }你可能发现了我完全没有写表名、列名、主键这些配置这是 EF Core 的“约定优于配置”在起作用。属性名是Id或类型名Id就会被识别为主键类里面有外键属性和导航属性就能推断出关系字符串默认映射成数据库里的可变长度文本。约定能覆盖八成以上日常场景剩下两成再交给 Fluent API 或特性标签来处理。2.3 连接字符串与 Program.cs 注册连接字符串建议放在appsettings.json不要硬编码到代码里。这里有个细节JSON 里反斜杠要转义所以 LocalDB 的(localdb)\MSSQLLocalDB在 JSON 里要写成(localdb)\\MSSQLLocalDB。{ ConnectionStrings: { DefaultConnection: Server(localdb)\\MSSQLLocalDB;DatabaseBlogDemoDb;Trusted_ConnectionTrue;TrustServerCertificateTrue;MultipleActiveResultSetstrue } }然后到Program.cs里注册 DbContextvar builder WebApplication.CreateBuilder(args); builder.Services.AddDbContextAppDbContext(options { options.UseSqlServer( builder.Configuration.GetConnectionString(DefaultConnection)); options.EnableSensitiveDataLogging(); }); var app builder.Build(); // 开发期可以在这里直接迁移生产环境建议用 SQL 脚本 using (var scope app.Services.CreateScope()) { var db scope.ServiceProvider.GetRequiredServiceAppDbContext(); db.Database.Migrate(); } app.Run();EnableSensitiveDataLogging()会让日志里显示 SQL 参数的明文值只看本地开发用上生产前删掉。MultipleActiveResultSetstrue的意思是允许多条结果集后面做懒加载、在一个连接里同时跑多个查询时会省很多心。2.4 第一次建库直接用 EnsureCreated 还是上迁移第一次建库有两条路。一条是临时方案在代码里写db.Database.EnsureCreated()。它的逻辑是数据库不存在就创建数据库和全部表数据库已存在则什么都不做。这个方案上手极快但有个致命问题它不创建__EFMigrationsHistory迁移历史表。后续一旦你改用迁移或者加了新字段EF 会完全失忆不清楚哪些表是它建的、哪些结构已经应用过于是必然出事。另一条从一开始就上迁移这也是我强烈推荐的路线。先在 Data 项目里生成第一个迁移文件dotnet ef migrations add InitialCreate --project BlogDemo.Data --startup-project BlogDemo.Api这个命令执行后Data 项目里会出现一个Migrations文件夹里面有InitialCreate.cs、InitialCreate.Designer.cs和一个项目快照文件。它们会记录“当前模型长什么样”下次你改实体类时EF 就能基于快照算出 diff。然后执行dotnet ef database update --project BlogDemo.Data --startup-project BlogDemo.Api这时在 SQL Server Object Explorer 里刷新一下就能看到Blog、Post、Comment三张业务表和一张系统表__EFMigrationsHistory。__EFMigrationsHistory里记录着已经应用过的迁移名字以后每次database updateEF 都会对比这张表只执行没跑过的迁移。所以结论很简单临时 Demo 用EnsureCreated()可以但凡是打算多活几天的项目第一次建库就直接用迁移。从 0 到 1 多敲两条命令省掉后面无数麻烦。3. 让表结构真正符合业务实体和映射设计的几个实战细节Code First 不是把实体写完就完事。约定能给你一张能用的表但不一定是一张好用的表。拿 Fluent API 调整映射才是建库这一步真正有价值的部分。3.1 主键、外键与导航属性EF 是怎么知道“一对多”的EF Core 识别关系的规则很直白Blog类里有一个ICollectionPost PostsPost类里有一个Blog Blog和一个int BlogId它就推断这是“一个博客对多篇文章”的关系外键就是BlogId。你不用写任何配置也能得到正确的关系。但现实里表关系往往没那么听话我的习惯是在OnModelCreating里把重要关系显式写出来既是给自己的文档也是防止团队里有人不小心改坏约定。protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.EntityPost(entity { entity.HasKey(p p.Id); entity.HasOne(p p.Blog) .WithMany(b b.Posts) .HasForeignKey(p p.BlogId) .OnDelete(DeleteBehavior.Cascade); }); modelBuilder.EntityComment(entity { entity.HasKey(c c.Id); entity.HasOne(c c.Post) .WithMany(p p.Comments) .HasForeignKey(c c.PostId) .OnDelete(DeleteBehavior.Cascade); }); }一致性在于如果Post里同时有BlogId和Blog导航属性EF 就认为关系是必填的也就是数据库里外键会带上NOT NULL和级联删除。如果你去掉BlogId只有导航属性它会自动在表里生成BlogId。推荐做法是显式保留外键属性因为很多业务逻辑要直接读外键值不需要导航属性导致一次额外查询。3.2 字段长度和精度线上金额少小数位的教训约定虽然省事但默认值有时并不符合业务。比如 SQL Server 默认把字符串映射成nvarchar(max)这在现代数据库里性能问题不大但你会失去数据库层面的长度校验。一个Title如果业务上明确规定 200 个字符以内就该在模型里给它一个HasMaxLength(200)。这样生成的列就是nvarchar(200)超长字符串在数据库层就直接被拦住了。更典型的坑是decimal。默认精度是decimal(18,2)也就是最多 18 位、小数保留 2 位。如果你的业务涉及金额而且精确到分那没问题但涉及汇率、折扣、单价等更高精度场景2 位小数会把数据悄悄截断。有一次我们线上对账金额始终差了几厘钱查到最后就是字段精度不够数据库存的0.3333被截成0.33。从那以后涉及财务字段我都在 Fluent API 里明确指定精度modelBuilder.EntityOrder(entity { entity.Property(o o.Amount) .HasPrecision(18, 4); });HasPrecision(18, 4)生成的列就是decimal(18,4)。顺带说一句枚举字段默认存 int如果希望数据库里直接存字符串要用 EF Core 的HasConversionstring()。做报表的人看到1、2看不出意义存Pending、Paid就直观多了。3.3 索引、唯一约束与默认值在代码里把数据库约束立好很多人建表时不建索引等线上慢查询出来再补。Code First 的好处是索引可以跟着模型走代码评审阶段就能看到哪些字段被加了索引。比如博客名理论上不该重复可以直接加唯一索引modelBuilder.EntityBlog(entity { entity.Property(b b.Name).HasMaxLength(100).IsRequired(); entity.HasIndex(b b.Name).IsUnique(); });生成的数据库里会有一个唯一索引重复插入时直接抛异常。CreatedAt这类创建时间字段没必要每次在应用层手动赋值可以用数据库默认值modelBuilder.EntityBlog(entity { entity.Property(b b.CreatedAt) .HasDefaultValueSql(GETDATE()); });这里补充一个经验如果CreatedAt用了数据库默认值那通过 EF Core 插入时EF 会刷新实体的CreatedAt属性吗默认不会你需要显式ValueGeneratedOnAdd()告诉 EF“这个列的默认值由数据库生成”插入后 EF 才会回读。不加内存里的对象和数据库里的值可能不一致后面处理时容易踩坑。3.4 级联删除的隐藏炸弹多路径级联怎么处理级联删除本身很香但 SQL Server 有个很磨人的限制一个表被多条级联路径同时指向时建表直接报错“may cause cycles or multiple cascade paths”。举个例子评论同时关联文章和博客文章删除时评论级联删除博客删除时文章级联删除评论又跟着文章级联删除这就出现了多路径SQL Server 会拒绝创建表。我见过不少团队第一次跑迁移就死在这里然后一顿乱猜误以为是 EF 版本问题。解决办法通常是让其中一条路径不级联modelBuilder.EntityComment(entity { entity.HasOne(c c.Post) .WithMany(p p.Comments) .HasForeignKey(c c.PostId) .OnDelete(DeleteBehavior.Restrict); });改用Restrict后数据库层就不会形成多条级联路径。业务上真的需要删文章时可以在应用层手动先删评论或者做软删除。记住一个原则级联删除图里不要出现环一旦报多路径级联的错误先检查实体关系里面有没有“三角形”结构。4. 数据库建好之后才是开始迁移的正确打开方式数据库第一次建出来只是起点。真正考验人的是业务上线之后表结构还要改改完还要保证各环境一致。这一节讲清楚迁移在项目生命周期里到底怎么用。4.1 为什么 EnsureCreated 在项目上线后是颗定时炸弹EnsureCreated()只负责“建库”不负责“演进”。它会检查数据库是否存在存在就直接跳过根本不管现有表结构和你最新实体类是不是对得上。也就是说你本地用EnsureCreated建了库然后给实体加了新字段删掉数据库再EnsureCreated才能看到新列不删的话程序一查询新字段就报“列名无效”。更麻烦的是EnsureCreated不会创建__EFMigrationsHistory。如果某天你决定切换成迁移方案EF 会认为这个数据库里没有任何迁移被应用过于是从第一个迁移开始执行试图创建已经存在的表于是必然报错。数据库里有数据你又不能随便删库重来这时候就非常被动了。所以我再次强调新项目从第一天就用Migrate()。Migrate()同样能建库但它会把每次变更记录到迁移历史表里后续模型改了多少它都清清楚楚。4.2 一次加字段、一次改索引的完整迁移过程假设线上版本跑了两周业务方要给博客表加一个CreatedBy字段用于记录创建人。你只需要在实体类里加属性public class Blog { public string? CreatedBy { get; set; } }然后生成迁移dotnet ef migrations add AddCreatedByToBlog --project BlogDemo.Data --startup-project BlogDemo.ApiEF 会对比当前实体模型和上一版模型快照生成一个带Up和Down的迁移文件。Up是应用本次变更Down是回滚protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.AddColumnstring( name: CreatedBy, table: Blog, type: nvarchar(50), maxLength: 50, nullable: true); } protected override void Down(MigrationBuilder migrationBuilder) { migrationBuilder.DropColumn( name: CreatedBy, table: Blog); }郑重提醒每个迁移文件的 Up/Down 都必须人工检查。生成器偶尔会把nullable搞反或者在你只改一个字段时夹杂一些莫名其妙的索引重建。迁移文件是跟着代码上线的不是生成完就锁进保险箱的东西。本地验证没问题后执行更新dotnet ef database update --project BlogDemo.Data --startup-project BlogDemo.Api生产环境我不建议直接跑dotnet ef database update更稳的做法是让 DBA 执行生成的 SQL 脚本dotnet ef migrations script --idempotent --output deploy.sql--idempotent会生成一个可以反复执行的脚本脚本内部会自动检测哪些迁移已经应用过不会重复执行。这样你在发布窗口重复跑几遍也安全。4.3 种子数据让所有环境都有同一份基础数据表结构建好了可很多系统没有基础数据就跑不起来比如状态字典、权限角色、菜单配置。Code First 处理种子数据最标准的方式是HasData。在OnModelCreating里直接声明modelBuilder.EntityBlog().HasData(new Blog { Id 1, Name 博客系统初始化数据, CreatedAt new DateTime(2024, 1, 1) });注意两点第一HasData里的数据必须给主键指定明确值否则生成迁移时 EF 不知道要插入哪一行第二指定的值会被写进迁移文件以InsertData的方式执行所以所有环境从空库跑到最新迁移后基础数据自然就都在了。这个方案只适合静态基础数据。如果种子数据需要读 Excel、调用外部接口或者带复杂业务逻辑那就别挤进迁移里单独写一个DbInitializer在应用启动时判断“表里没有数据再插入”并且要保证幂等。迁移的职责是结构业务种子走应用层两者别混在一起。4.4 多环境和团队协作迁移文件不是给自己看的团队协作时最头疼的是迁移文件冲突。两个人同时改实体类各自生成了迁移等你合并代码时发现两个迁移文件里的内容互相打架。我的经验是一次只允许一个迁移“在途”。每个人拉最新代码先跑一次database update确保自己本地已经和最新模型同步然后才在自己的开发分支上加迁移。合并时如果发现冲突通常只需要删掉自己手头那个还没合入的迁移重新生成一次让迁移和主分支模型对齐。另一个容易忽略的点是“启动项目”和“迁移所在项目”要分清。大项目会拆成BlogDemo.Data和BlogDemo.Api执行命令时必须同时指定--project和--startup-project否则迁移文件会生成到启动项目里或者设计时无法找到连接字符串。养成固定在命令后跟这两个参数的习惯能避开很多混乱。5. 建库过程中最容易翻车的五个瞬间下面这些坑不是我一个人踩过的它们几乎包揽了新人提问区里关于 Code First 建库的 80% 问题。我把出错的瞬间、原因和排查过程一起写清楚。5.1 dotnet ef 命令找不到缺的是全局工具和 Design 包现象很明确在项目目录敲dotnet ef migrations add InitialCreate终端直接报“dotnet: 未识别的命令”或提示找不到dotnet-ef。原因有两个。第一个你没装全局工具。执行一次dotnet tool install --global dotnet-ef装完如果还是找不到检查环境变量是否包含了~/.dotnet/tools这个目录重启终端再试。这个问题在第一台新电脑上几乎必现。第二个你装了工具但项目里缺Microsoft.EntityFrameworkCore.Design包。这时候命令能识别但会报类似“Your startup project doesnt reference Microsoft.EntityFrameworkCore.Design”的错。这个包的作用是让 EF 在项目里找到设计时的上下文工厂没有它迁移命令无法构建你的 DbContext。在 Web 启动项目里装上它就行。5.2 迁移文件跑到别的地方启动项目没选对有次我在项目里执行dotnet ef migrations add AddStatusField没带任何参数结果迁移文件生成到了 Web 项目的根目录里而不是 Data 项目的Migrations文件夹。原因就是项目结构里有多个项目EF 默认把迁移放在包含 DbContext 的项目里但连接字符串从启动项目读取两边不一致时行为就会很怪。规范做法是每次写全dotnet ef migrations add AddStatusField --project BlogDemo.Data --startup-project BlogDemo.Api这里的--project是迁移文件要生成的项目--startup-project是启动项目用来读配置和构建服务。如果启动项目里配了对的DbContext就不需要写那么长但在多层项目里多打几个字绝对比事后搬文件省时间。5.3 数据库已经存在Update-Database 却一直报错典型场景是项目一开始用了EnsureCreated()数据库和表都已经在本地建好了后来改成迁移执行Update-Database时报“There is already an object named Blog in the database”或者“表已存在”。原因前面说过EnsureCreated建的库没有迁移历史EF 认为要从第一个迁移开始于是尝试创建已经存在的表直接撞墙。如果在开发环境最干脆的办法是删库重来。dotnet ef database drop可以删或者直接在 SSMS 里删除。但如果库里有测试数据或者已经上了生产事情就复杂了。给生产环境做基线的大致思路是让第一个迁移只记录当前模型状态而不重新建表然后手动往__EFMigrationsHistory里插入这条迁移记录让 EF 认为它已经应用过了。这个操作比较精细不建议没搞懂迁移机制的人贸然做。这也是为什么我反复强调尽早选边站要么全程迁移要么永远临时方案别混用。5.4 本地能建、服务器建不了权限和连接串的坑本地用 Windows 集成认证连 LocalDB 很顺畅一上服务器就开始翻车最常见的两个问题。第一个是登录账号没有创建数据库的权限。迁移命令本质上是在数据库里执行CREATE DATABASE、CREATE TABLE这类 DDL这需要dbcreator或较高的权限。很多公司的履行账号只有读写业务表的权限没有 DDL 权限那在服务器上直接跑Update-Database就会失败。解决方式是用有建库权限的账号执行一遍迁移或 SQL 脚本之后应用运行只用普通读写权限的账号。第二个是连接字符串里的证书问题。高版本 SQL Server 默认要求加密连接如果你的连接串没写TrustServerCertificateTrue本地可能没事服务器上因为证书验证失败直接拒绝连接。连接串可以加上Server192.168.1.10,1433;DatabaseBlogDemoDb;User Idapp_user;Passwordxxx;TrustServerCertificateTrue;EncryptTrue密码如果包含分号整个密码片段要用引号包起来这个细节经常让人在非交互环境里排查半天。5.5 模型改了没迁移运行时才报“列名无效”这种问题最冤代码在本地跑得好好的部署上去一查数据就报Invalid column name CreatedBy。通常不是环境差异而是你改了实体类但没生成迁移或者迁移没在主分支上合入。EF 运行时生成的 SQL 会引用实体类里的所有映射列而数据库里根本没有那列。排查思路是先跑一次--verbose日志确认 EF 生成的 SQL 是不是包含了某个不存在的列然后检查 Data 项目里有没有对应的迁移文件最后对比数据库的__EFMigrationsHistory和最新迁移确认识别最后一步。这个坑本质上还是流程问题。核心纠正动作是实体类改动与迁移文件生成必须在同一个提交里完成哪怕迁移文件看起来很大也要一起提交。等到运行时被数据库报错打疼成本已经翻了好几倍。6. 我现在的固定操作流程和几条个人习惯踩过这么多坑之后我现在每个新项目都走同一套流程这里分享给你。首先新建解决方案时就把Data类库独立出来实体类、DbContext、迁移文件、各种IEntityTypeConfigurationT全部放里面。启动项目只负责读配置和注册服务。这样项目不管最后部署成 Web 服务、控制台程序还是微服务迁移逻辑都不会散落。其次在OnModelCreating里我不再写“上帝方法”——不会把所有实体配置堆在一个几百行的方法里。每个实体单独写一个配置类比如BlogConfiguration : IEntityTypeConfigurationBlog然后统一modelBuilder.ApplyConfigurationsFromAssembly(typeof(AppDbContext).Assembly);这个反射加载一行代码能替换掉原来业务上几十行手工调用项目变大后维护成本会明显下降。第三从第一张表开始就使用迁移而不用EnsureCreated。我见过太多项目一开始图省事最后上线前花一周处理数据库结构对不上的问题。迁移在我看来不只是“建库工具”更是数据库结构的版本控制系统越早用越省心。第四每次生成完迁移我会打开迁移文件检查三件事Up里有没有意料之外的建表或删列Down有没有写反有没有生成多余的索引。如果同事在旁边我还会让他扫一眼因为写迁移的人和写普通代码的人一样会惯性忽略自己的错误。最后一个小习惯发布前用dotnet ef migrations script --idempotent生成脚本文档把它当成发布工单的一部分。就算生产环境不允许执行脚本这个文档也能让 DBA 明确知道这次发布要改动哪些表。Code First 并不是把数据库设计这件事直接扔给框架。恰恰相反它把数据库结构变更从“在管理工具里点鼠标”变成了“在代码评审里被审查”。一旦你适应了这种工作方式就会发现数据库的可重现性、团队协作和信息透明度都上了一个台阶。希望这篇经验能让你在新项目里第一次建库就少走弯路。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门