# Ты, наверное, ошибаешься в перемещениях ЭФП...

Працює `MigrateAsync()` Під час запуску? Ви надаєте вашим програмам права власника бази даних і сподіваєтеся, що нічого не станеться. Є кращий спосіб - Міграції EC дозволяють вам запускати міграцію як контрольований крок CI, утримуючи ваш комп' ютерний додаток. Але ось дещо: іноді " wrong " насправді непоганий. Давайте розглянемо, коли слід використовувати кожен підхід.

<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) | [БундлесCity in New Jersey USA](https://learn.microsoft.com/en-us/ef/core/managing-schemas/migrations/applying?tabs=dotnet-core-cli#bundles)

[TOC]

# Wrong" way (який я використовую)

Цей блог використовує `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** - Швидка ітерація б'є церемонія.
- **Особисті проекти** - Низький радіус вибуху, без чутливих даних
- **Докер- комбінація середовищ dev** - Зручність перемагає
- **Прострочення** - Схема постійно змінюється

## Коли це не так

- **Декілька екземплярів програм** - Расові умови шаленіють
- **Відчутливі дані** - PII, фінансовий, регульований = належне розділення потрібно
- **Виробництво за допомогою реальних користувачів** - Невдача міграції =outage

# Правий шлях: EC Bundle

Смуга EF - це самодостатній виконуваний файл, у якому містяться ваші компіляції. Думайте про те, що `dotnet ef database update` пакунок у окремому місці `.exe`.

**Чому перемагають пучки:**

- **Немає залежностей під час виконання** - Ціль не потребує SDK або EF CLI
- **Належне роз'єднання** - App ніколи не потрібна `db_owner`; Робить лише інструмент запуску CI, лише під час виконання
- **Видимість CI** - Невдачі, які показуються у каналах трубопроводів, а не поховані під час запуску програм
- **Забезпечення зворотним зв' язком** - Переміщення зазнає невдачі?
- **Ідепотент** - Стежить за застосуванням, виконує тільки те, що потрібно

> **Примітка:** Для безпеки виробництва, використовуйте [Керований профіль](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? не хочете перевірити перед тим, як натискати? Зберіть вузли локально.

**Випадки використання:** Тестування перед CI, DBA whichoff (вбудований приклад, без використання 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
```

Перехоплює синтаксичні помилки, обмеження, проблеми з FK - all *до* СІ або виробництво.

## Швидкодія збирання

**Покоління бундлів є повільним** - 30+секунд на великих проектах.

- Створювати вручну під час локальної перевірки
- Створювати у CI лише під час виконання, не всі PR
- Вузли кешу, якщо міграцій не змінено

Якщо ви справді бажаєте автоматичного створення, додайте ціль MSBuild:

```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](https://learn.microsoft.com/en-us/ef/core/managing-schemas/migrations/applying?tabs=dotnet-core-cli#sql-scripts).

### Скрипти SQL у CI

```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
```

## DACPAC (лише сервер SQL)

[DACPACs](https://learn.microsoft.com/en-us/sql/relational-databases/data-tier-applications/data-tier-applications) є *заснований на станах* неDescription of a condition. Do not translate key words (# V1S #, # V1 #,) *заснований на міграціях*. Ви визначаєте бажану схему, а SqlPackage розшифрує її у базі даних призначення.

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

**Прос:** Схема як код, автоматичне створення різниці, керування всім (таблями, переглядами, SP, індексами), інструментами для роботи з підприємцями.

**Збори:** Тільки сервер SQL, схема у двох місцях (EF Modes + SQL project), рушій різниці робить сумнівний вибір, перейменування стовпчиків виглядає як floop+add.

Див. [Документи SqlPackage](https://learn.microsoft.com/en-us/sql/tools/sqlpackage/sqlpackage).

## Таблиця порівняння

Д'Ап-доріжка Ап-пайлі ДБА ДБО ДБІ ДБ
|----------|----------|---------------|---------------------|--------------|-------------------|
| `MigrateAsync()` Д_ д. д. д. д. д. д. д. д. д. д. д. д. д.
Д-р Харріс:
♪SQ ZOGE-DBA керується середовищами ♪ Ні' з `--idempotent` Д. Так.
*------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

# Підказки

## Попався файл дизайнера

Міграції працюють у вашій місцевості, але не на ЦРУ? **Перевірте, чи передано обидва файли:**

- `20231115_AddUserTable.cs` - Код міграцій
- `20231115_AddUserTable.Designer.cs` - Знімок моделі

Відсутній файл Designer = безмовна помилка.

## Декілька DbContexts

```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.

## IDesignTimeDbContextFactory

EC- інструменти повинні миттєво втілювати ваш DbContext. Якщо ваш DbContext знаходиться у окремому проекті або має складний запуск, реалізацію [`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? "

Наведена вище, але коротка версія: пучки - це портативні артефакти. Ваш крок впровадження не потребує EC CLI, вихідного коду або роздільної здатності у режимі дизайну. Такий же вузол працює у тестах, застійах і прод - нуль дрейфів.

## "Хіба це не забагато для маленької програми?"

Можливо. а радіус вибуху низький - `MigrateAsync()` Але як тільки ви додаєте ще один розробник, чутливі дані або багато середовищ, снопи платять самі за себе.

## "А як щодо зворотних поворотів?"

EC не виконує автоматичних розкладок. Параметри:

- Створити `Down()` міграція і запуск (але вам слід записати її)
- Відновити з резервної копії
- Записати вручну міграцію, щоб скасувати зміни

Для критичних систем: спочатку тест перелітає на клон бази даних.

## "Чи можу я керувати міграціями у контейнері Kubernetes init?"

Так. Контейнер " Бондле + 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.

## "А як щодо FluentMigrator / DbUp / інших інструментів?"

Вони працюють чудово. Смуги ЄФ є еФ-нативним рішенням, але [FluentMigrator](https://fluentmigrator.github.io/) і [DbUp](https://dbup.readthedocs.io/) Ключова відмінність: ці інструменти є специфічними для міграцій, в той час як EF-групи походять від вашої EC-моделі.

## "Моя DBA хоче переглянути всі SQL перед запуском"

Користування `--idempotent` скрипти:

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

DBA рецензування і схвалення.

- Запустити скрипт вручну або
- Після схвалення запустіть вузол (що робить те саме)

## "Як мені впоратися з міграціями з нульовим спадом?"

Це питання стратегії розповсюдження, а не питання міграції.

1. Робить міграцію зворотно сумісною (додайте стовпчики непридатними, не перейменовуйте)
2. Впорядкувати новий код, який працює як зі старою, так і новою схемою
3. Переміщення запуску
4. Вкласти код, який використовує лише нову схему
5. Очищення старих стовпчиків під час пізнішої міграції)

Бонделі не вирішують це - вони просто роблять крок 3 більш передбачуваним.