# 你可能做 EF 移民错误...

运行中 `MigrateAsync()` 启动时? 您正在给您的应用程序数据库所有者权利, 希望不会出任何差错。 有一个更好的方法 — EF 迁移捆让您将迁移作为受控的 CI 步骤, 保护您的生产应用程序的安全 。 但问题是: 有时“ 错误” 方式其实是好的。 我们来探索何时使用每种方法 。

<datetime class="hidden">2025-11-23T18:39</datetime>

<!--category--  Entity Framework, Migrations, GitHub, CI -->
**官方文件 :** [移徙概况](https://learn.microsoft.com/en-us/ef/core/managing-schemas/migrations/) | [应用移民](https://learn.microsoft.com/en-us/ef/core/managing-schemas/migrations/applying) | [套套件](https://learn.microsoft.com/en-us/ef/core/managing-schemas/migrations/applying?tabs=dotnet-core-cli#bundles)

[TOC]

# "Wrong" 方式(我使用的)

此博客使用 `MigrateAsync()` 开始的时候我要告诉你不要使用的方法。这就是为什么我不介意, 以及为什么它可能不适合你。

在我的 `Program.cs` 我有以下文件:

```csharp
    using (var scope = app.Services.CreateScope())
    {
        var blogContext = scope.ServiceProvider.GetRequiredService<IMostlylucidDBContext>();
        await blogContext.Database.MigrateAsync();
    }
```

[`MigrateAsync()`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.relationaldatabasefacadeextensions.migrateasync) 需要时适用待处理的迁移,并创建数据库。

1. **启动受抚养人** 移民失败了 你的应用程序启动不了
2. **侵犯安全** - 你的应用程序需要 `db_owner` 右键。 您刚刚给您的运行时间应用了 键来投放表格 。

我为什么要逃避它:公共数据, 单一的多克网络, 个人项目。 **你可能办不到**

## 当运行时间移民顺利时

- **当地dev** - 快速迭代击打仪式
- **个人项目 个人项目** - 低爆炸半径,无敏感数据
- **Dockker- compos dev 环境** - 便利赢赢
- **原型设计** - 气候在不断变化

## 当他们不

- **多个应用程序实例** - 种族条件
- **敏感数据** - PII,财务,受管制=要求的适当离职
- **实际用户生产** - 失败的移徙=失业

# 正确途径:EF捆绑

EF捆绑是一个自足的可执行文件, 包含您所编译的迁移 。 `dotnet ef database update` 组合成独立 `.exe`.

**为什么捆绑赢:**

- **没有运行时间依赖性** - 目标不需要SDK或EFCLI
- **适当离职** - 申请从不需要 `db_owner`;只有在部署期间才使用 CID 跑者
- **CIC可见度** - 输油管道记录显示故障,没有埋在启动应用程序中
- **滚回安全** 在错误的代码部署之前就停止部署
- **具有同等能力者** - 跟踪应用内容,只运行需要的东西

> **注:** 用于生产级安保,使用 [管理身份](https://learn.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview) 而不是连接字符串。 但捆绑仍然是从跑步移民中迈出的一大步。

## GitHub 行动实例

```yaml
      - name: Install EF Core tools
        run: dotnet tool install --global dotnet-ef

      - name: Add EF tools to PATH
        run: echo "$HOME/.dotnet/tools" >> $GITHUB_PATH

      - name: Generate EF migration bundle
        run: |
          dotnet ef migrations bundle \
            --project ${{ env.WEB_PROJECT }} \
            --output efbundle.exe \
            --configuration ${{ env.BUILD_CONFIGURATION }} \
            --runtime ${{ env.RUNTIME_IDENTIFIER }} \
            --context AdminDbContext \
        env:
          AdminSite__ConnectionString: ${{ secrets.PROD_SQL_CONNECTIONSTRING }}

      - name: Run EF migration bundle
        run: |
          ./efbundle.exe
        env:
          AdminSite__ConnectionString: ${{ secrets.PROD_SQL_CONNECTIONSTRING }}
```

捆绑从环境变量读取连接字符串, 并应用待定的迁移。 已经应用过吗 ? 它只是成功退出 。

# 本地套件

没有线人吗 想在推前先试一试吗?

**使用案例:** 在CI前测试, DBA 上交( 独立前端, 不需要 SDK ) , 中转部署, 调试 `--verbose`.

## 创建捆绑

```bash
# Install EF CLI (once)
dotnet tool install --global dotnet-ef

# Basic bundle
dotnet ef migrations bundle \
    --project Mostlylucid.DbContext \
    --startup-project Mostlylucid \
    --output efbundle.exe

# Self-contained (includes runtime - portable to machines without .NET)
dotnet ef migrations bundle \
    --project Mostlylucid.DbContext \
    --startup-project Mostlylucid \
    --output efbundle.exe \
    --self-contained

# Cross-platform (e.g., build on Windows, deploy to Linux)
dotnet ef migrations bundle \
    --project Mostlylucid.DbContext \
    --startup-project Mostlylucid \
    --output efbundle \
    --runtime linux-x64
```

## 正在运行您的套装

```bash
# Using default connection string from appsettings.json
./efbundle.exe

# Override with a specific connection string
./efbundle.exe --connection "Host=localhost;Database=mostlylucid;Username=postgres;Password=secret"

# Using an environment variable (matches your config key)
$env:ConnectionStrings__DefaultConnection="Host=localhost;..." # PowerShell
export ConnectionStrings__DefaultConnection="Host=localhost;..." # Bash
./efbundle.exe
```

## 有用的套装选项

```bash
# See what migrations would be applied without running them
./efbundle.exe --dry-run

# Verbose output for debugging
./efbundle.exe --verbose

# Apply migrations up to a specific migration (useful for testing)
./efbundle.exe --target-migration "20231115_AddUserTable"

# Combine options
./efbundle.exe --verbose --dry-run
```

## 本地测试工作流量

```bash
# 1. Create migration
dotnet ef migrations add AddNewFeature \
    --project Mostlylucid.DbContext \
    --startup-project Mostlylucid

# 2. Build bundle
dotnet ef migrations bundle \
    --project Mostlylucid.DbContext \
    --startup-project Mostlylucid \
    --output efbundle.exe

# 3. Dry run first
./efbundle.exe --dry-run --verbose

# 4. Run for real
./efbundle.exe --verbose

# 5. Broken? Remove and retry
dotnet ef migrations remove \
    --project Mostlylucid.DbContext \
    --startup-project Mostlylucid
```

Catchs 语法错误、违反限制、FK问题 -- -- 所有 *之前* CI或生产。

## 构建绩效

**捆包的代代慢慢** - 30+秒的大型项目,不要在每一个建筑上产生。

- 本地测试时手工生成
- 只是在部署期间才在CI产生,而不是每个PR
- 如果移民没有改变, 缓存捆包

如果您真的想要自动生成, 请添加一个 MSBulild 目标 :

```xml
<Target Name="BuildMigrationBundle">
  <Exec Command="dotnet ef migrations bundle --output $(OutputPath)efbundle.exe --force" />
</Target>
```

然后: `dotnet build -t:BuildMigrationBundle`

# 混合方法

最好的两个世界:当地方便,生产安全。

```csharp
if (builder.Environment.IsDevelopment())
{
    using var scope = app.Services.CreateScope();
    var context = scope.ServiceProvider.GetRequiredService<IMostlylucidDBContext>();
    await context.Database.MigrateAsync();
}
// Production: CI pipeline runs the bundle
```

# 套装替代物

## SQL 脚本

生成普通 SQL 而非可执行文件 。 对 DBA 审查和现有变革管理程序来说, 卓越 。

```bash
# All migrations
dotnet ef migrations script --output migrations.sql

# Idempotent (safe to run multiple times) - USE THIS
dotnet ef migrations script --idempotent --output migrations.sql

# Range of migrations
dotnet ef migrations script FromMigration ToMigration --output migrations.sql
```

**专业:** 完全可见度,任何SQL客户端都可以运行,版本控制方便,DBA审批工作流程。

**关节 :** 不自动跟踪( 使用) `--idempotent`),手动执行,如果修改脚本,可能会漂移。

见见 [SQL 脚本上的公务 docs](https://learn.microsoft.com/en-us/ef/core/managing-schemas/migrations/applying?tabs=dotnet-core-cli#sql-scripts).

### CI 中的 SQL 脚本

```yaml
- name: Generate and apply migrations
  run: |
    dotnet ef migrations script --idempotent --output migrations.sql
    # SQL Server
    sqlcmd -S ${{ secrets.DB_SERVER }} -d ${{ secrets.DB_NAME }} -i migrations.sql
    # Or PostgreSQL
    PGPASSWORD=${{ secrets.DB_PASSWORD }} psql -h ${{ secrets.DB_HOST }} -f migrations.sql
```

## DACCPAC (仅SQL服务器)

[发发援援协调会](https://learn.microsoft.com/en-us/sql/relational-databases/data-tier-applications/data-tier-applications) 是 *基于状态的* 否 *以移徙为基础的移徙人口*。您根据目标数据库定义想要的图案, SqlPackage diffs 。

```bash
SqlPackage.exe /Action:Publish /SourceFile:MyDatabase.dacpac /TargetConnectionString:"..."
```

**专业:** Schema作为代码、自动生成、处理一切(表格、视图、SP、索引)、企业工具。

**关节 :** 仅 SQL 服务器, 两个地方的图案( EF 模型 + SQL 项目) , diff 引擎的选择有问题, 列重命名看起来像 drop+add 。

见见 [Sql 软件包文档](https://learn.microsoft.com/en-us/sql/tools/sqlpackage/sqlpackage).

## 比较比较表

最佳办法 需要 .NET 自动追踪 应用 .DBA 友好 . .
|----------|----------|---------------|---------------------|--------------|-------------------|
| `MigrateAsync()` * 发展/小型项目 * * 是(临时) * * 是 * 否 * * 是 * * * 是 * * * 是
* EF bundles * CI/CD 输油管 * * 无(自足) * * 是 * 有* * 有* * 有* * * 有* * * 有* * 否(自足) * * 否(自足) * * 有* * 有* * 有* * 有* * 有* * 有* * 有* * 有* * 有* * 有*
QQL SQL 脚本  DBA 控制环境 `--idempotent` 是的,是的,是的,是的,是的。
DACPAC  SQL 服务器企业

# 提示提示

## 找到设计器文件

移民在当地工作,但不在移民中心工作? **检查您所执行的两个文件 :**

- `20231115_AddUserTable.cs` - 移徙法
- `20231115_AddUserTable.Designer.cs` - 模型快照

缺少设计器文件 = 沉默失败 。

## 多个 DbContext

```bash
dotnet ef migrations bundle --context BlogDbContext --output blog-migrations.exe
dotnet ef migrations bundle --context IdentityDbContext --output identity-migrations.exe
```

## 连接字符串优先级

1. `--connection` 参数参数
2. 环境变数
3. `appsettings.json`

在 CI 中使用环境变量。

## IDignTimeDbcontextFortorial 设计设计工具

EF 工具需要对 DbContext 进行即时化 DbContext 。如果您的 DbCtext 在一个单独的项目中,或者启动过程复杂,请执行 [`IDesignTimeDbContextFactory<T>`](https://learn.microsoft.com/en-us/ef/core/cli/dbcontext-creation?tabs=dotnet-core-cli#from-a-design-time-factory):

```csharp
public class AdminDbContextFactory : IDesignTimeDbContextFactory<AdminDbContext>
{
    public AdminDbContext CreateDbContext(string[] args)
    {
        var config = new ConfigurationBuilder()
            .SetBasePath(Directory.GetCurrentDirectory())
            .AddJsonFile("appsettings.json", optional: true)
            .AddEnvironmentVariables()
            .AddUserSecrets<AdminDbContextFactory>()
            .Build();

        var connectionString = config["AdminSite:ConnectionString"]
            ?? throw new InvalidOperationException("Missing connection string");

        var optionsBuilder = new DbContextOptionsBuilder<AdminDbContext>();
        optionsBuilder.UseSqlServer(connectionString, sql => sql.CommandTimeout(120));

        return new AdminDbContext(optionsBuilder.Options);
    }
}
```

使用时间: DbContext 在不同的项目中使用, 复杂的启动, 设计时需要用户保密 。

# 怎么样... ?

我收到的常见问题和退路

## "为什么不只是运行 `dotnet ef database update` "在CI"吗?

上面写着, 但短版本是: 捆包是便携式的手工艺品。 您的部署步骤不需要 EF CLI 、 源代码或设计时间分辨率 。 同样的捆包在测试、 中转和测试中运行 - 零漂移 。

## "这难道不是为了一个小应用而过度杀戮吗?"

如果你是独唱,数据是公开的 爆炸半径低 `MigrateAsync()` 。但当您添加第二个开发者、敏感数据或多个环境时,捆包会为自己付费。

## "反弹怎么办?"

EF不自动回滚。 选项 :

- 生成 `Down()` 迁移至他处,然后迁移至他所居住的地方。
- 从备份恢复
- 写入手动迁移以撤销更改

对于关键系统:首先对数据库克隆进行迁移测试。

## "我能在库伯涅茨的集装箱里做移民吗?"

是。 套装 + Init 集装箱是一个固态模式:

```yaml
initContainers:
  - name: migrate
    image: myapp:latest
    command: ["./efbundle.exe"]
    env:
      - name: ConnectionStrings__Default
        valueFrom:
          secretKeyRef:
            name: db-secrets
            key: connection-string
```

App 容器等待 Init 完成 。

## "流利移民/DbUp/其他工具呢?"

EF捆绑是EF本地的解决方案,但是 [流流者](https://fluentmigrator.github.io/) 和 [弹顶](https://dbup.readthedocs.io/) 关键区别是:这些是针对移民的工具,而EF包来自您现有的EF模式。

## "我的DBA想在SQL运行前 对所有SQL进行审查"

使用使用 `--idempotent` 脚本 :

```bash
dotnet ef migrations script --idempotent --output migrations.sql
```

DBA审查和批准。

- 手动运行脚本, 或
- 一旦批准, 运行捆包( 做同样的事情)

## "我如何处理零停机时间的迁移?"

这是一个部署战略问题,不是移民问题。

1. 使移徙向后兼容( 添加列为无效, 不重命名)
2. 部署新的代码,处理新旧和旧的策略
3. 流动移徙
4. 只使用新计划布局代码
5. 清理( 在以后的移民中丢弃旧的列)

套装不能解决问题 只会让第三步更可预测