
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导读本指南围绕 FSHFullStackHerodotnet-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 restore到dotnet ef migrations add、再到DbMigrator apply的完整增删改查迁移闭环理解快照snapshot陷阱、上下文命名、迁移命名规范与各类高频故障的排查方法。一、核心事实迁移工程的组织方式先读再动手在使用任何迁移命令之前需要先理解这套 Starter Kit 与每个项目自带迁移的常规做法截然不同的三点设计所有迁移都集中在一个项目里src/Host/FSH.Starter.Migrations.PostgreSQL项目文件。它通过 ProjectReference 引用各个模块的运行时项目例如Modules.Auditing、Modules.Identity、Modules.Multitenancy、Modules.Webhooks、Modules.Billing、Modules.Catalog、Modules.Tickets、Modules.Chat、Modules.Notifications、Modules.Files因此dotnet ef可以解析到全部模块的 DbContext。按模块/上下文分目录存放目录名与上下文一一对应每个目录内既有按时间戳命名的迁移文件对{时间戳}_{名称}.cs与对应的.Designer.cs也有各自独立的{X}DbContextModelSnapshot.cs快照。从仓库现状可以确认的目录与上下文对应关系如下Audit/→AuditDbContextBilling/→BillingDbContextCatalog/→CatalogDbContextChat/→ChatDbContextEventing/→EventingDbContextFiles/→FilesDbContextIdentity/→IdentityDbContextMultiTenancy/→TenantDbContext租户目录注意不是MultitenancyDbContextNotifications/→NotificationsDbContextTickets/→TicketsDbContextWebhooks/→WebhookDbContext启动项目是 API 宿主执行dotnet ef相关命令时必须显式传--project迁移项目、--startup-projectAPI 宿主、--context {X}DbContext、--output-dir {X}四件套。真实的上下文名称清单文档明确给出了全部真实上下文名务必原样使用IdentityDbContext、TenantDbContext租户目录——不是 MultitenancyDbContext、AuditDbContext、BillingDbContext、CatalogDbContext、TicketsDbContext、FilesDbContext、ChatDbContext、NotificationsDbContext、WebhookDbContext。注意WebhookDbContext是单数 Webhook不带 s而FilesDbContext、TicketsDbContext是复数拼错会导致 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.jsonStep 1 — 先构建快照陷阱dotnet ef migrations add读取的是当前快照snapshot而快照是从一次构建重新生成的。如果你在修改实体/EF 配置后跳过构建直接生成迁移就会基于过期的快照生成导致修改静默丢失。此外migrations remove会重写快照因此只能移除最新的迁移且移除后必须重新构建。dotnet build src/FSH.Starter.slnxStep 2 — 添加迁移三个参数缺一不可--project迁移项目、--startup-projectAPI 宿主、--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需要重点扫描的风险点详见第三节的迁移审查清单意外的删表/删列、向已有表添加无默认值的非空列、以及重命名被实现成 dropadd造成数据丢失。必要时手工修改迁移文件或调整模型。Step 4 — 应用优先使用规范化路径会依次迁移租户目录与每个租户的模块 schemadotnet 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}—— 新增实体如AddCategories、AddProductsAdd{Property}To{Entity}—— 给已有实体加属性Create{Index}Index—— 新增索引Rename{Old}To{New}—— 重命名从仓库现有的迁移文件可以看到大量实际例子例如20260430033849_AddCategories、20260512142004_AddProductImages、20260624152035_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 lockPostgresMigratorLock.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 — 逐租户迁移 可选种子从IMultiTenantStoreAppTenantInfo读取全部租户可用--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同时执行 SeedAsyncseed仅对每个租户执行 SeedAsync 步骤seed-demo预置演示租户acme、globex的用户、目录、工单与聊天内容。仅限开发环境——除非DOTNET_ENVIRONMENTDevelopment否则拒绝执行并以退出码 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 #1349AddEventingCore注册了 EventingDbContext 及其 IDbInitializer因此框架的 outbox/inbox 表会与每个模块的 schema 一同创建。四、迁移审查清单应用前必须检查的三类数据风险migration-helper要求生成迁移脚本后扫描以下风险点这也是生产环境最常见的三类悄悄丢数据事故源头意外的删表/删列确认Down()与Up()都要看尤其是那些你没有主动删除却被 EF 推断为删除的对象常见于重命名实体、改导航属性后 EF 认为是删除重建。向已有表添加无默认值的非空列对已有数据的表这会直接导致插入/迁移失败。要么给默认值要么拆成两步先加可空列、回填、再改非空。重命名被实现成 dropaddEF 无法自动推断重命名往往会生成删旧列 加新列存量数据全部丢失。审查时若发现此类脚本应手工修改为RENAME COLUMN。一个真实教训就在仓库里Webhooks模块的20260624152035_RenameWebhookSecretHashToProtectedSecret它对应的是对已有列的重命名迁移正是这种审查场景的样板。五、故障排查速查表文档给出的排查表是这篇工作流文档的精华完整继承如下症状原因 → 修复No DbContext was found / 多个上下文始终显式传--context {X}DbContextBuild failed先执行dotnet build src/FSH.Starter.slnx迁移落到了错误目录加--output-dir {X}与上下文现有目录保持一致迁移中缺少变更你在migrations add前没有构建快照过期ef 找不到新模块的上下文迁移项目必须引用该模块的运行时项目结合 database.md 规则 还可补充两条与本主题相关的操作纪律migrations remove作用于快照先完整构建再做migrations add否则可能丢失之前的迁移。多租户隔离默认开启BaseDbContext会对每个实体自动应用租户查询过滤器仅在实现IGlobalEntity的实体如BillingPlan、ImpersonationGrant、Outbox/InboxMessage上豁免。子类 DbContext 若重写OnModelCreating必须在最后调用base.OnModelCreating(modelBuilder)否则自动过滤器会丢失——这条同样影响迁移生成时的模型快照。六、回到文档定位migration-helper 与 create-migration 的分工最后澄清一下这份工作流文档在仓库中的定位方便你后续直接复用create-migration 技能持有权威的增/审/应用配方上文第二节的四步流程与结尾 Checklist是执行时的唯一标准。migration-helper 工作流本文主题文档负责补充周边事实与故障排查——即第一节的事实清单、第三节的 DbMigrator 原理、第四节的审查要点和第五节的排查表。database.md 规则定义实体基类、租户隔离、AsNoTracking、导航集合子实体值生成等 EF 约定是触碰实体与 DbContext 前的必读。三者配合的完整闭环是改实体 →dotnet build→dotnet 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),仅供参考