10.8. Міграції (Migrations)
10.8. Міграції (Migrations)
Вступ: Еволюція бази даних
Код змінюється — і база даних повинна змінюватися разом з ним. Коли ви додаєте нову властивість до класу Book, відповідний стовпець повинен з'явитися в таблиці. Коли змінюєте тип із string на int — стовпець має змінити тип. Міграції EF Core — це механізм, який переводить вашу C# модель у SQL-команди для оновлення бази даних.
dotnet-ef CLI.Code-First vs Database-First
| Підхід | Хто головний | Інструмент | Коли |
|---|---|---|---|
| Code-First | C#-класи → БД | Migrations | Нові проєкти |
| Database-First | БД → C#-класи | Scaffold | Legacy БД |
Code-First (рекомендований)
Ви пишете C#-класи, EF Core генерує SQL:
C# Model → Migration File → SQL Script → Database
Database-First
Існуюча БД, EF Core генерує C#-класи:
dotnet ef dbcontext scaffold "Server=...;Database=LibraryDb;..." Microsoft.EntityFrameworkCore.SqlServer
Ми зосередимось на Code-First, оскільки це стандарт для нових проєктів.
Робочий цикл міграцій
Створення міграції
# Після зміни моделі
dotnet ef migrations add AddBookPageCount
EF Core:
- Аналізує поточну модель (C#-класи та DbContext)
- Порівнює з попереднім snapshot
- Генерує Migration file з
Up()таDown()
Застосування міграції
# Оновити БД до останньої міграції
dotnet ef database update
# Оновити до конкретної міграції
dotnet ef database update AddBookPageCount
# Відкотити до попередньої
dotnet ef database update PreviousMigrationName
Видалення міграції
# Видалити останню (ще не застосовану) міграцію
dotnet ef migrations remove
Структура файлу міграції
public partial class AddBookPageCount : Migration
{
// Up — що зробити при оновленні
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "PageCount",
table: "Books",
type: "int",
nullable: false,
defaultValue: 0);
}
// Down — що зробити при відкаті
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "PageCount",
table: "Books");
}
}
Розбір:
Up()— SQL для оновлення бази до цієї версії.ALTER TABLE Books ADD PageCount INT NOT NULL DEFAULT 0.Down()— SQL для відкату.ALTER TABLE Books DROP COLUMN PageCount.
EF Core також генерує snapshot-файл (LibraryContextModelSnapshot.cs), який зберігає поточний стан моделі.
Data Seeding (початкові дані)
HasData у OnModelCreating
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Author>().HasData(
new Author { Id = 1, Name = "Роберт Мартін", Country = "США" },
new Author { Id = 2, Name = "Мартін Фаулер", Country = "Великобританія" },
new Author { Id = 3, Name = "Ерік Еванс", Country = "США" }
);
modelBuilder.Entity<Book>().HasData(
new { Id = 1, Title = "Чистий код", Year = 2008, Isbn = "ISBN-1", AuthorId = 1, IsAvailable = true },
new { Id = 2, Title = "Рефакторинг", Year = 1999, Isbn = "ISBN-2", AuthorId = 2, IsAvailable = true },
new { Id = 3, Title = "DDD", Year = 2003, Isbn = "ISBN-3", AuthorId = 3, IsAvailable = true }
);
}
HasData вимагає явного Id. Це тому, що seed дані включаються в міграцію як INSERT з конкретними Id. EF Core перевіряє, чи не змінилися seed дані, при кожній новій міграції.Окремий Seeder
Для складніших даних — окремий клас:
public static class DatabaseSeeder
{
public static void Seed(LibraryContext context)
{
if (context.Authors.Any()) return; // Вже заповнено
var author = new Author { Name = "Роберт Мартін", Country = "США" };
context.Authors.Add(author);
context.SaveChanges();
context.Books.AddRange(
new Book { Title = "Чистий код", Year = 2008, Isbn = "ISBN-1", AuthorId = author.Id },
new Book { Title = "Clean Architecture", Year = 2017, Isbn = "ISBN-2", AuthorId = author.Id }
);
context.SaveChanges();
}
}
Типові операції міграцій
// У методі Up() можна додавати кастомний SQL:
protected override void Up(MigrationBuilder migrationBuilder)
{
// Стандартні операції EF Core
migrationBuilder.CreateTable(...);
migrationBuilder.AddColumn(...);
migrationBuilder.AlterColumn(...);
migrationBuilder.DropColumn(...);
migrationBuilder.CreateIndex(...);
migrationBuilder.AddForeignKey(...);
migrationBuilder.RenameColumn(...);
// Кастомний SQL
migrationBuilder.Sql("UPDATE Books SET IsAvailable = 1 WHERE IsAvailable IS NULL");
migrationBuilder.Sql("CREATE VIEW ActiveBooks AS SELECT * FROM Books WHERE IsDeleted = 0");
}
Міграції у CI/CD
Генерація SQL-скрипту
# Ідемпотентний скрипт (безпечний для повторного запуску)
dotnet ef migrations script --idempotent -o migration.sql
Цей скрипт перевіряє таблицю __EFMigrationsHistory і виконує тільки нові міграції.
Migration Bundle (EF Core 6+)
# Створити виконуваний файл для міграцій
dotnet ef migrations bundle --self-contained -r linux-x64
# Запустити на сервері
./efbundle --connection "Server=prod;Database=LibraryDb;..."
Практичні завдання
Рівень 1: Базовий
Завдання 1.1: Перша міграція
- Створіть модель
Product(Name, Price, Category). dotnet ef migrations add InitialCreate.- Подивіться згенерований файл.
dotnet ef database update.
Завдання 1.2: Еволюція
- Додайте
Descriptionдо Product. - Створіть міграцію
AddProductDescription. - Перевірте Up() та Down().
- Відкотіть міграцію, перевірте БД.
Рівень 2: Практичний
Завдання 2.1: Data Seeding
- Додайте HasData з 5 категоріями та 10 продуктами.
- Створіть міграцію, перевірте SQL.
- Змініть один seed — створіть нову міграцію, перевірте різницю.
Завдання 2.2: Кастомний SQL
- Додайте в міграцію створення VIEW.
- Додайте INSERT для довідникових даних.
- Згенеруйте ідемпотентний SQL-скрипт.
Резюме
Code-First
Migration File
Data Seeding
CI/CD