news 2026/9/17 21:25:08

dotnet-starter-kit EF Core 迁移实战指南:集中式 Migrations 项目、DbMigrator 应用与故障排查手册

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
dotnet-starter-kit EF Core 迁移实战指南:集中式 Migrations 项目、DbMigrator 应用与故障排查手册

dotnet-starter-kit EF Core 迁移实战指南:集中式 Migrations 项目、DbMigrator 应用与故障排查手册

【免费下载链接】dotnet-starter-kitProduction Grade Cloud-Ready .NET 10 Starter Kit (Web API + React Client) with Multitenancy Support, and Clean/Modular Architecture that saves roughly 200+ Development Hours! All Batteries Included.项目地址: https://gitcode.com/GitHub_Trending/do/dotnet-starter-kit

导读

本指南围绕 FSH(FullStackHero)dotnet-starter-kit 仓库中面向开发流程的migration-helper工作流文档展开,系统讲解这套生产级 .NET 10 Starter Kit 中 EF Core 迁移的管理方式:全部迁移集中在src/Host/FSH.Starter.Migrations.PostgreSQL一个项目中、按模块/上下文分目录存放;数据库不在 API 启动时迁移,而是由独立的FSH.Starter.DbMigrator宿主进程统一执行,并以 Postgres advisory lock 串行化多租户迁移。读完本文,你将掌握从dotnet tool restoredotnet ef migrations add、再到DbMigrator apply的完整增删改查迁移闭环,理解快照(snapshot)陷阱、上下文命名、迁移命名规范与各类高频故障的排查方法。


一、核心事实:迁移工程的组织方式(先读再动手)

在使用任何迁移命令之前,需要先理解这套 Starter Kit 与"每个项目自带迁移"的常规做法截然不同的三点设计:

  1. 所有迁移都集中在一个项目里src/Host/FSH.Starter.Migrations.PostgreSQL(项目文件)。它通过 ProjectReference 引用各个模块的运行时项目,例如Modules.AuditingModules.IdentityModules.MultitenancyModules.WebhooksModules.BillingModules.CatalogModules.TicketsModules.ChatModules.NotificationsModules.Files,因此dotnet ef可以解析到全部模块的 DbContext。
  2. 按模块/上下文分目录存放:目录名与上下文一一对应,每个目录内既有按时间戳命名的迁移文件对({时间戳}_{名称}.cs与对应的.Designer.cs),也有各自独立的{X}DbContextModelSnapshot.cs快照。从仓库现状可以确认的目录与上下文对应关系如下:
    • Audit/AuditDbContext
    • Billing/BillingDbContext
    • Catalog/CatalogDbContext
    • Chat/ChatDbContext
    • Eventing/EventingDbContext
    • Files/FilesDbContext
    • Identity/IdentityDbContext
    • MultiTenancy/TenantDbContext(租户目录,注意不是MultitenancyDbContext
    • Notifications/NotificationsDbContext
    • Tickets/TicketsDbContext
    • Webhooks/WebhookDbContext
  3. 启动项目是 API 宿主:执行dotnet ef相关命令时,必须显式传--project(迁移项目)、--startup-project(API 宿主)、--context {X}DbContext--output-dir {X}四件套。

真实的上下文名称清单

文档明确给出了全部真实上下文名,务必原样使用:

IdentityDbContextTenantDbContext(租户目录——不是 "MultitenancyDbContext")、AuditDbContextBillingDbContextCatalogDbContextTicketsDbContextFilesDbContextChatDbContextNotificationsDbContextWebhookDbContext

注意WebhookDbContext是单数 "Webhook"(不带 s),而FilesDbContextTicketsDbContext是复数,拼错会导致 "No DbContext was found"。

工具版本锁定

dotnet-ef被固定在 .config/dotnet-tools.json 中,版本为10.0.2rollForward: false,不自动向前滚动)。首次使用前必须先执行:

dotnet tool restore

同文件还锁定了fullstackhero.cli10.0.0-rc.1),说明仓库对脚手架类 CLI 同样采用版本锁定策略。

关键认知:API 启动时不迁移数据库

这是最容易踩坑的认知点——数据库不会在 API 启动时被自动迁移UseHeroMultiTenantDatabases()只负责注册 Finbuckle 的租户解析机制,它不做任何迁移动作。真正执行迁移的是DbMigrator宿主(见下文第三节)。如果你改了实体却只重启 API 而不跑迁移,新字段永远不会出现在数据库里。


二、规范化流程:create-migration 技能(唯一标准配方)

migration-helper明确声明:新增/应用迁移的唯一标准配方是仓库内的create-migration技能,即 .agents/skills/create-migration/SKILL.md。migration-helper本身负责补充"周边事实与故障排查"。完整的四步流程如下。

Step 0 — 恢复锁定工具(首次)

dotnet tool restore # dotnet-ef 锁定在 .config/dotnet-tools.json

Step 1 — 先构建(快照陷阱)

dotnet ef migrations add读取的是当前快照(snapshot),而快照是从一次构建重新生成的。如果你在修改实体/EF 配置后跳过构建直接生成迁移,就会基于过期的快照生成,导致修改静默丢失。此外,migrations remove重写快照,因此只能移除最新的迁移,且移除后必须重新构建。

dotnet build src/FSH.Starter.slnx

Step 2 — 添加迁移

三个参数缺一不可:--project(迁移项目)、--startup-project(API 宿主)、--context {X}DbContext;再加--output-dir {X}确保迁移落在该上下文对应的模块目录(与现有目录保持一致):

dotnet ef migrations add {MigrationName} \ --project src/Host/FSH.Starter.Migrations.PostgreSQL \ --startup-project src/Host/FSH.Starter.Api \ --context {X}DbContext \ --output-dir {X}

关于dotnet ef为何能对BaseDbContext正常工作,技能文档补充说明:因为 4 参数构造函数可以由启动宿主的 DI 满足。仓库中 BaseDbContext 正是这套多租户持久化体系的基础。

Step 3 — 审查生成的 SQL(应用前必做)

dotnet ef migrations script --idempotent \ --project src/Host/FSH.Starter.Migrations.PostgreSQL \ --startup-project src/Host/FSH.Starter.Api \ --context {X}DbContext

需要重点扫描的风险点(详见第三节的"迁移审查清单"):意外的删表/删列、向已有表添加无默认值的非空列、以及重命名被实现成 drop+add(造成数据丢失)。必要时手工修改迁移文件或调整模型。

Step 4 — 应用

优先使用规范化路径(会依次迁移租户目录与每个租户的模块 schema):

dotnet run --project src/Host/FSH.Starter.DbMigrator -- apply dotnet run --project src/Host/FSH.Starter.DbMigrator -- list-pending # 先预览

本地单上下文的开发场景,也可以直接用dotnet ef database update --context {X}DbContext --project … --startup-project …

命名规范

文档给出明确的迁移命名约定:

  • Add{Entity}—— 新增实体(如AddCategoriesAddProducts
  • Add{Property}To{Entity}—— 给已有实体加属性
  • Create{Index}Index—— 新增索引
  • Rename{Old}To{New}—— 重命名

从仓库现有的迁移文件可以看到大量实际例子,例如20260430033849_AddCategories20260512142004_AddProductImages20260624152035_RenameWebhookSecretHashToProtectedSecret

新增模块的额外要求

如果新建模块,除了迁移文件外还需要:在迁移项目中新增{X}/目录,并且迁移项目要引用该模块的运行时项目(参照上文列出的 csproj ProjectReference 列表,新增模块可参考 add-module 技能)。否则dotnet ef将找不到新模块的上下文。


三、应用环节的深度原理:DbMigrator 宿主的完整执行链路

文档强调"DbMigrator宿主负责应用迁移:先迁移租户目录(TenantDbContext),再迁移每个租户各自的模块 schema,通过 Postgres advisory lock 串行化"。结合 Program.cs 源码,可以把这条链路展开为四个阶段:

Step 0 — 等待数据库就绪

PostgresMigratorLock.WaitForDatabaseAsync以指数退避(初始 1 秒,最大 10 秒,总时限 2 分钟)轮询目标数据库是否可连接,专门应对 Aspire/K8s 冷启动时 Postgres 尚未就绪的情况;若 2 分钟内不可达则抛出TimeoutException并以退出码 1 结束。若连接失败但 SQLSTATE 为3D000(数据库不存在),视为服务器可达、数据库待创建,直接放行让 EF 在首次MigrateAsync时建库。

Step 0b — 获取 advisory lock

PostgresMigratorLock.AcquireAsync使用固定 64 位键0xFE514EC0DEB1ADE4执行SELECT pg_advisory_lock(@key)。这是会话级锁:并发的 migrator 进程会在此阻塞排队;持有锁的连接被 dispose(或进程崩溃)时锁自动释放,不会产生孤儿锁。首次运行且数据库不存在时降级为无操作锁(NoopLock),后续运行再获取真实锁。完整实现见 PostgresMigratorLock.cs。

Step 1 — 迁移租户目录

在独立 scope 中取得TenantDbContext,通过GetPendingMigrationsAsync检查待迁移项;list-pending只打印清单,apply则调用MigrateAsync。首次启动时若根租户(MultitenancyConstants.Root,可参考 MultitenancyConstants.cs)不存在,会自动写入,保证后续"每个租户"遍历至少有一个租户可迭代。

Step 2 — 逐租户迁移 + 可选种子

IMultiTenantStore<AppTenantInfo>读取全部租户(可用--tenant <id>限定单个租户),对每个租户调用ITenantService.MigrateTenantAsync。该服务的实现位于 TenantService.cs,其核心逻辑是:在每个租户的 scope 中遍历所有IDbInitializer,逐一调用initializer.MigrateAsync(cancellationToken)。这就是"每个模块各自迁移 schema"的真正机制——每个模块的 DbInitializer 各自负责本模块上下文在该租户数据库上的迁移。

退出码与失败处理

迁移成功打印[migrator] finished successfully.并返回 0;任何异常被顶层 catch 捕获,打印[migrator] FAILED: {类型}: {消息}与堆栈后返回 1,最后在 finally 中优雅停掉宿主以冲刷日志。这份契约在 MigratorCommand.cs 的帮助文本中也有明确说明。

DbMigrator 命令行参考

从 MigratorCommand.cs 的解析器与帮助文本可提取完整语法:

dotnet run --project src/Host/FSH.Starter.DbMigrator -- [verb] [options]
动词说明
apply应用待处理迁移(默认动词)。加--seed同时执行 SeedAsync
seed仅对每个租户执行 SeedAsync 步骤
seed-demo预置演示租户(acme、globex)的用户、目录、工单与聊天内容。仅限开发环境——除非DOTNET_ENVIRONMENT=Development,否则拒绝执行并以退出码 1 结束
list-pending只打印待处理迁移,不应用任何东西
选项说明
--tenant <id>只作用于单个租户 id(默认全部租户)
--catalog-only跳过逐租户环节,只迁移租户目录
--seedapply 之后调用ITenantService.SeedTenantAsync
-h, --help打印帮助文本

设计取舍:为什么不用 API 启动迁移

Program.cs 顶部的注释点明了关键理由:DbMigrator 作为部署步骤运行,因此可以使用具备 DDL 权限的高权限连接串;API 运行时则使用低权限连接串,天然满足最小权限原则。同时 migrator 关闭了 OpenTelemetry、CORS、OpenAPI、Jobs、Mailing、SSE、Realtime、Quotas、FeatureFlags、Idempotency 等运行时能力,只保留 Persistence、Multitenancy 与 Caching(部分模块构造依赖IDistributedCache,无 Redis 时回退内存实现),并在启动前剥离所有BackgroundService,避免它们在表创建之前抢先读写而触发42P01错误。这些处理在 Program.cs 中有完整注释说明。

一个容易忽视的细节

EventingDbContext也随逐租户循环创建 schema(源码注释引用了 issue #1349):AddEventingCore注册了 EventingDbContext 及其 IDbInitializer,因此框架的 outbox/inbox 表会与每个模块的 schema 一同创建。


四、迁移审查清单:应用前必须检查的三类数据风险

migration-helper要求生成迁移脚本后扫描以下风险点,这也是生产环境最常见的三类"悄悄丢数据"事故源头:

  1. 意外的删表/删列:确认Down()Up()都要看,尤其是那些你没有主动删除却被 EF 推断为删除的对象(常见于重命名实体、改导航属性后 EF 认为是删除重建)。
  2. 向已有表添加无默认值的非空列:对已有数据的表,这会直接导致插入/迁移失败。要么给默认值,要么拆成两步(先加可空列、回填、再改非空)。
  3. 重命名被实现成 drop+add:EF 无法自动推断重命名,往往会生成"删旧列 + 加新列",存量数据全部丢失。审查时若发现此类脚本,应手工修改为RENAME COLUMN

一个真实教训就在仓库里:Webhooks模块的20260624152035_RenameWebhookSecretHashToProtectedSecret,它对应的是对已有列的重命名迁移,正是这种审查场景的样板。


五、故障排查速查表

文档给出的排查表是这篇工作流文档的精华,完整继承如下:

症状原因 → 修复
"No DbContext was found" / 多个上下文始终显式传--context {X}DbContext
"Build failed"先执行dotnet build src/FSH.Starter.slnx
迁移落到了错误目录--output-dir {X}(与上下文现有目录保持一致)
迁移中缺少变更你在migrations add前没有构建(快照过期)
ef 找不到新模块的上下文迁移项目必须引用该模块的运行时项目

结合 database.md 规则 还可补充两条与本主题相关的操作纪律:

  • migrations remove作用于快照:先完整构建再做migrations add,否则可能丢失之前的迁移。
  • 多租户隔离默认开启BaseDbContext会对每个实体自动应用租户查询过滤器;仅在实现IGlobalEntity的实体(如BillingPlanImpersonationGrantOutbox/InboxMessage)上豁免。子类 DbContext 若重写OnModelCreating必须在最后调用base.OnModelCreating(modelBuilder),否则自动过滤器会丢失——这条同样影响迁移生成时的模型快照。

六、回到文档定位:migration-helper 与 create-migration 的分工

最后澄清一下这份工作流文档在仓库中的定位,方便你后续直接复用:

  • create-migration 技能:持有权威的增/审/应用配方(上文第二节的四步流程与结尾 Checklist),是执行时的唯一标准。
  • migration-helper 工作流:本文主题文档,负责补充周边事实与故障排查——即第一节的事实清单、第三节的 DbMigrator 原理、第四节的审查要点和第五节的排查表。
  • database.md 规则:定义实体基类、租户隔离、AsNoTracking、导航集合子实体值生成等 EF 约定,是触碰实体与 DbContext 前的必读。

三者配合的完整闭环是:改实体 →dotnet builddotnet ef migrations add(四件套参数)→ 脚本审查 →DbMigrator list-pending预览 →DbMigrator apply应用 → 必要时dotnet tool restore复位锁定工具。这套流程既避免了"API 启动自动迁移"在生产环境的高权限风险,也通过集中式迁移项目 + 每模块独立快照 + advisory lock 串行化,让多租户、多模块的大规模 schema 演进保持可控与可回滚。

【免费下载链接】dotnet-starter-kitProduction Grade Cloud-Ready .NET 10 Starter Kit (Web API + React Client) with Multitenancy Support, and Clean/Modular Architecture that saves roughly 200+ Development Hours! All Batteries Included.项目地址: https://gitcode.com/GitHub_Trending/do/dotnet-starter-kit

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/17 21:24:32

Notepad-- 快速上手指南:编码、文件对比与批量查找一次配好

Notepad-- 快速上手指南&#xff1a;编码、文件对比与批量查找一次配好 【免费下载链接】notepad-- 一个支持windows/linux/mac的文本编辑器&#xff0c;目标是做中国人自己的编辑器&#xff0c;来自中国。 项目地址: https://gitcode.com/GitHub_Trending/no/notepad-- …

作者头像 李华
网站建设 2026/9/17 21:19:46

SVN仓库迁移:UUID、dump/load、relocate与客户端配置

凌晨两点&#xff0c;运维群里弹出一条消息&#xff1a;下周机房调整&#xff0c;SVN 服务器要换机器&#xff0c;仓库根目录从原来的/svn挪到/repos&#xff0c;让大家提前把本地代码处理好。这句话看着轻描淡写&#xff0c;落到每个开发头上就是一场小型事故——有人手一抖把…

作者头像 李华
网站建设 2026/9/17 21:19:18

四节传送带PLC控制:顺序启动、逆序停止与故障联锁编程

简介&#xff1a;围绕PLC四节传送带控制系统展开的毕业设计文档&#xff0c;面向机电一体化、电气自动化等专业的在校学生及初学PLC控制的技术人员&#xff0c;可用于课程设计选题、梯形图编写练习与答辩材料整理。压缩包内仅1个doc文档&#xff0c;约683KB&#xff0c;篇幅紧凑…

作者头像 李华