使用 EF Core 更新与修改生产数据库
使用 EF Core 的 Code First,在设计阶段,直接使用 Database.EnsureCreated()和 EnsureDeleted()可以快速删除、更新最新的数据结构。由于没有什么数据,删除的风险非常低。但是对于已经投入生产的数据库,这个方法就绝对不可行了。
考虑以下场景:
项目已经上线,一直使用本地测试数据库进行开发,本地已经增加和修改了较多数据库表结构,线上数据庞大且实时更新,现在测试完毕需要进行上线。
如果需要更新生产数据库,我能想的有两种方法:
一、从一开始就使用 Migration
从数据库开始设计的时候,就使用 EF Migration,保证数据库能够与代码同步,不过操作的时候,需要极为小心,务必要检查生成的更新数据库代码,直接连接生产数据库,
需要注意的事项:
从一开始就使用 Migration,任何时候都不要使用 Context.Database.EnsureCreated 或者 EnsureDeleted 语句。
使用 Add-migration 之后,不要删除生成的 Migration 文件,这些文件记录了数据结构的变化历史。
并不是所有的变化都能自动识别,比如“修改表列名称大小写”,这种情况很多时候生成的数据是执行删除然后再新建,和我们重命名的初衷相去甚远。因此要特别检查 migrationBuilder.Drop 相关的页面。
二、使用 Scaffold
如果一开始就没有使用 migration 进行同步的话,那么使用 EF Core 将无法直接更新,我们需要变通一下:
逆向数据库到模型
首先需要数据库的数据结构逆向到模型,我们使用 Scaffold 就可以了,详细文档就可以查看这里,需要注意的是,我们的场景下,已经有修改好的 DataContext 与 Model,在进行 scaffold 的过程中,一定要指定 outputdir 和 context,不要和当前的文件冲突。
根据自己的喜好,选择是否采用-DataAnnotations,另外也可以使用-table 指定需要修改的表,没有被指定的表,将保持原样。默认 EF Core 会按照自己的命名规则重新命名,如果你想保留自己的套路,那么使用-UseDatabaseNames 参数。
Add-Migration
输出的模型我指定放在 Models 文件夹,原来的 Models 文件夹,我改成了 Models1,并且更换了命名空间以保证项目现在能够正常编译。
导出的模型与 DbConext:Models.Models 命名空间,Models 文件夹
新模型与 DbConext:Models 命名空间,Models1 文件夹接下来运行 Add-Migration。
这样会在 Migrations 文件夹下面生成一个 snapshot 和一个 migration 文件。snapshot 是当前数据库的跟踪,另外一个是运用 update-database 时系统会执行的操作。里面有一个 Up()和一个 Down()方法,Up 是执行更新时 EF 对数据库的操作,Down 是回滚当前更改。由于这是第一次执行 add-migration,EF Core 会认为数据库现在还是空的,因此两个方法都有大量的语句,我们删除所有 create 和 drop 相关的语句,我这边是全部删除了,只留下空方法。
应用迁移,同步
前面准备工作已经到位了,这一步将直接操作数据库了。使用 update-database 将当前的 migration 更新到数据库,由于我们现在的数据结构和生产数据库的数据结构一模一样,实际上我们不需要执行什么操作(删除了 Up、Down 内部的代码),执行 Update-Database 只是让 EF Core 将 Models 和生产数据库建立联系。
我理解只是添加__EFMigrationsHistory 中的记录,以便 EF Core 后续追踪。
修改模型内容
将 Models1 中的文件覆盖 Models 中的文件,由于类型命名的差异,可能会提示一些错误,按照自己的习惯修改就好了。接下来是循序渐进,一点点修改模型,并经常 add-migration,观察生成的语句是否正常。
由于我使用了 Identity,在数据中有对应的 AspNet 开头的表,这些表我并不在本系统中使用(其他系统需要用),因此我删除了对应的模型、snapshot、DbContext 记录,运行 Add-Migration,生成了如下文件:
说明现在已经能够正常跟踪我们的修改了,不过我这里需要保留对应的表,因此删除 up 与 down 的所有内容。
注意以下几点:
1.更新模型名称
如果使用 fluentAPI,那么模型对应的表名称会直接在 fluentAPI 中直接指定,只修改模型的名称没有任何效果。修改的话,可以修改对应的 fluentAPI,或者换用 Annotation
2.提示找不到 constraint
对于修改主键、索引等内容的情况,如果不是通过 EF Core 建立的数据库,那么命名规则可能不一样。对于 postgresql 数据库,可以用这个查询名称,然后修改对应的 migration 文件内容即可。
3.复合主键的限制
对于使用两列或者以上列作为复合主键的情况,使用 EnsureCreated 方法是可以识别 Annotation 形式的主键的。
使用 Migration 的时候,这种形式无法识别,需要在 OnModelCreating()中,使用 fluentAPI:
4.Command 执行超时
默认 Command 执行的超时设置只有 30s,对一些大一点的表来说,是不太够的。可以设置:
增加命令执行的超时时间。
5.多个连接字符串的情况
如果程序使用了 appsettings.Development.json 之类的文件存储连接字符串,那么需要指定环境是 Production(生产数据库),否则可能还原到本地数据库去了。对于 nuget 包管理控制台(使用 update-database),执行:
对于使用 dotnet ef 工具集的,直接执行:
6.cannot be cast automatically to type
设计数据库表如果修改列的数据类型(比如从 varchar 到 integer),Postgresql 会提示这个问题,导致无法修改。可以在 migrationbuilder 中使用 sql,按照提示添加"USING "x"::integer"解决。但是这种方法还是不太优雅,手动处理 Up()之后,还需要处理 Down(),否则将无法正确还原。
可以使用分步的方法进行,假设我们需要将 Id 从 varchar 改成 int4。
添加一个字段 temp,类型为 int4,设置为[Key],然后删除 Id 字段。
添加并应用迁移
修改 temp 名称为 Id
添加并应用迁移
多次应用迁移
每次修改尽量少一点,然后 update-database,这样更容易发现问题,对于有
这种提示的,一定要检查生成语句中 Drop 相关的语句。
本地数据库与生产数据库,都有__EFMigrationsHistory 记录相关的迁移情况。在生产与本地数据库中进行切换时,不用担心顺序问题,Update-Database 会一个个应用迁移直到最新。
总结
使用 Migration 能够降低数据库同步中很多工作量,合理利用,可以对生产用的数据库进行热更新。
注:本文在.NET 6,EF Core 6 下测试通过。
相关内容拓展:(技术前沿)
近 10 年间,甚至连传统企业都开始大面积数字化时,我们发现开发内部工具的过程中,大量的页面、场景、组件等在不断重复,这种重复造轮子的工作,浪费工程师的大量时间。
针对这类问题,低代码把某些重复出现的场景、流程,具象化成一个个组件、api、数据库接口,避免了重复造轮子。极大的提高了程序员的生产效率。
推荐一款程序员都应该知道的软件 JNPF 快速开发平台。这是一个基于 Java Boot/.Net Core 构建的简单、跨平台快速开发框架。前后端封装了上千个常用类,方便扩展;集成了代码生成器,支持前后端业务代码生成,实现快速开发,提升工作效率;框架集成了表单、报表、图表、大屏等各种常用的 Demo 方便直接使用;后端框架支持 Vue2、Vue3。
体验官网:https://www.jnpfsoft.com?infoq
还没有了解低代码这项技术可以赶紧体验学习!
版权声明: 本文为 InfoQ 作者【高端章鱼哥】的原创文章。
原文链接:【http://xie.infoq.cn/article/fd88b8dccab236d4b644666e3】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
评论