EFEF Core Handbook

ORTA

Migrations (Göçler)

C# modelinde yaptığın her değişikliği (yeni tablo, sütun ekleme, tip değiştirme) veritabanına taşıyan versiyonlama sistemidir. Git commit'leri gibi düşün — her migration bir "DB commit".

Veritabanı sağlayıcısı Bu sayfadaki eşleşen örnekleri seçilen sağlayıcıya göre gösterir.

Temel Komutlar

# İlk migration'ı oluştur
dotnet ef migrations add InitialCreate

# Veritabanına uygula
dotnet ef database update

# Yeni migration (ör. tablo ekleme)
dotnet ef migrations add AddOrdersTable

# Belirli bir migration'a geri dön (rollback)
dotnet ef database update AddOrdersTable

# Son migration'ı sil (henüz uygulanmamışsa)
dotnet ef migrations remove

# SQL script üret (production deploy için)
dotnet ef migrations script --idempotent -o migrate.sql

# Tüm migration'ların listesi
dotnet ef migrations list

Package Manager Console kullanıyorsan:

Add-Migration InitialCreate
Update-Database
Remove-Migration
Script-Migration -Idempotent

Migration dosyası neye benzer?

// Migrations/20250115_InitialCreate.cs (EF otomatik üretir)
public partial class InitialCreate : Migration
{
    protected override void Up(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.CreateTable(
            name: "Categories",
            columns: table => new
            {
                Id = table.Column<int>(nullable: false)
                    .Annotation("SqlServer:Identity", "1, 1"),
                Name = table.Column<string>(maxLength: 100, nullable: false)
            },
            constraints: table =>
            {
                table.PrimaryKey("PK_Categories", x => x.Id);
            });

        migrationBuilder.CreateTable(
            name: "Products",
            columns: table => new
            {
                Id = table.Column<int>(nullable: false)
                    .Annotation("SqlServer:Identity", "1, 1"),
                Name = table.Column<string>(maxLength: 200, nullable: false),
                CategoryId = table.Column<int>(nullable: false)
            },
            constraints: table =>
            {
                table.PrimaryKey("PK_Products", x => x.Id);
                table.ForeignKey("FK_Products_Categories_CategoryId",
                    x => x.CategoryId, "Categories", "Id",
                    onDelete: ReferentialAction.Restrict);
            });
    }

    protected override void Down(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.DropTable(name: "Products");
        migrationBuilder.DropTable(name: "Categories");
    }
}
// Migrations/20250115_InitialCreate.cs (EF otomatik üretir — Npgsql provider)
public partial class InitialCreate : Migration
{
    protected override void Up(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.CreateTable(
            name: "categories",
            columns: table => new
            {
                id = table.Column<int>(type: "integer", nullable: false)
                    .Annotation("Npgsql:ValueGenerationStrategy",
                        NpgsqlValueGenerationStrategy.IdentityAlwaysColumn),
                name = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: false)
            },
            constraints: table =>
            {
                table.PrimaryKey("pk_categories", x => x.id);
            });

        migrationBuilder.CreateTable(
            name: "products",
            columns: table => new
            {
                id = table.Column<int>(type: "integer", nullable: false)
                    .Annotation("Npgsql:ValueGenerationStrategy",
                        NpgsqlValueGenerationStrategy.IdentityAlwaysColumn),
                name = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: false),
                category_id = table.Column<int>(type: "integer", nullable: false)
            },
            constraints: table =>
            {
                table.PrimaryKey("pk_products", x => x.id);
                table.ForeignKey("fk_products_categories_category_id",
                    x => x.category_id, "categories", "id",
                    onDelete: ReferentialAction.Restrict);
            });
    }

    protected override void Down(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.DropTable(name: "products");
        migrationBuilder.DropTable(name: "categories");
    }
}

Migration Bundle (EF Core 6+) — Production Deploy

# Bundle = tek çalıştırılabilir dosya. dotnet ef tool'u production'a kurulmaz.
dotnet ef migrations bundle --self-contained -o efbundle.exe

# Production'da çalıştır
./efbundle.exe --connection "Server=prod;Database=MyDb;..."

CI/CD Pipeline'da Migration

# Azure DevOps / GitHub Actions örneği
steps:
  # 1. Migration bekleyen değişiklik var mı kontrolü
  - run: dotnet ef migrations has-pending-model-changes --no-build
  
  # 2. Idempotent script üret (her zaman güvenli — zaten uygulanmış olanları atlar)
  - run: dotnet ef migrations script --idempotent --no-build -o $(Build.ArtifactStagingDirectory)/migrate.sql
  
  # 3. Veya bundle üret
  - run: dotnet ef migrations bundle --no-build -o $(Build.ArtifactStagingDirectory)/efbundle

Production'da Migration Stratejileri

Strateji Ne Zaman Risk
dotnet ef database update Development Production'da kullanma
--idempotent SQL script Küçük-orta proje DBA review edebilir
Migration Bundle (efbundle) Otomatik deploy Hızlı ama review yok
Manual SQL + MigrationHistory insert Enterprise / DBA kontrolü Tam kontrol
Expand-Contract (Zero-downtime) Büyük production sistemi Downtime sıfır

Data Loss Uyarısı: Sütun kaldırma/tip değiştirme migration'larında EF uyarı verir ama engellemez. Production'da her migration'ı önce test ortamında çalıştırılmalı.

Zero-Downtime Migration (Expand-Contract Pattern)

Sütun rename veya kaldırma gibi breaking change'leri downtime olmadan yapmak için 2 adımlı deploy:

Deploy 1 (Expand):
  ─ Yeni sütunu EKLE (nullable, default ile)
  ─ Kod hem eski hem yeni sütuna yazar (dual-write)
  ─ Background job: eski veriyi yeni sütuna kopyalar

Deploy 2 (Contract):
  ─ Kod artık sadece yeni sütunu kullanır
  ─ Eski sütunu DROP et (artık referans yok)
// Adım 1: Expand — yeni sütun ekle, eski duruyor
migrationBuilder.AddColumn<string>("FullName", "Users", nullable: true);
migrationBuilder.Sql("UPDATE Users SET FullName = FirstName + ' ' + LastName");

// Adım 2: Contract (ayrı migration, ayrı deploy!)
migrationBuilder.DropColumn("FirstName", "Users");
migrationBuilder.DropColumn("LastName", "Users");

Kural: Bir migration'da hem ADD hem DROP yapma. Her biri ayrı deploy olmalı — arada kodun her iki sütunu da desteklediğinden emin ol.

Migration Geri Alma (Rollback)

# Son migration'ı geri al (development)
dotnet ef database update PreviousMigrationName

# Migration dosyasını sil (henüz apply edilmemişse)
dotnet ef migrations remove

# Production'da rollback — Down() metodu çalışır:
dotnet ef migrations script CurrentMigration PreviousMigration --idempotent -o rollback.sql
# DBA bu script'i review edip çalıştırır

Down() metodu güvenilir mi? EF otomatik Down üretir ama veri kaybı olabilir (DROP COLUMN geri gelmez). Critical migration'larda Down()'ı her zaman manual kontrol et.


Migration Akışı (Görsel)

C# Model Değişikliği dotnet ef migrations add ← .cs oluşur dotnet ef database update ← SQL uygulanır __EFMigrationsHistory ← Kayıt tutar DB

__EFMigrationsHistory tablosu (EF otomatik oluşturur):

MigrationId ProductVersion
20250115083000_InitialCreate 8.0.1
20250120140000_AddOrdersTable 8.0.1
20250201091500_AddIndexToProducts 8.0.1
PostgreSQL Migration Farkları

PostgreSQL Migration Farkları:

  • Komutlar aynı: dotnet ef migrations add, dotnet ef database update
  • __EFMigrationsHistory tablosu PostgreSQL'de de aynı şekilde oluşur (public schema'da)
  • Önemli farklar:
# PostgreSQL connection string ile migration:
dotnet ef database update -- --connection "Host=localhost;Database=mydb;Username=app;Password=***"

# Veya appsettings.json'dan otomatik alır
// Migration içinde provider-specific SQL:
protected override void Up(MigrationBuilder migrationBuilder)
{
    // EF Core her iki provider için doğru SQL üretir:
    migrationBuilder.CreateTable(
        name: "products",
        columns: table => new
        {
            Id = table.Column<int>(type: "integer", nullable: false)
                      .Annotation("Npgsql:ValueGenerationStrategy",
                          NpgsqlValueGenerationStrategy.IdentityAlwaysColumn),
            Name = table.Column<string>(type: "text", nullable: false),
            Price = table.Column<decimal>(type: "numeric(18,2)", nullable: false)
        },
        constraints: table => table.PrimaryKey("pk_products", x => x.Id));

    // Provider-specific raw SQL (sadece PG'de çalışır):
    if (migrationBuilder.ActiveProvider == "Npgsql.EntityFrameworkCore.PostgreSQL")
    {
        migrationBuilder.Sql("CREATE EXTENSION IF NOT EXISTS \"uuid-ossp\";");
        migrationBuilder.Sql("CREATE EXTENSION IF NOT EXISTS \"pg_trgm\";");
    }
}

PostgreSQL migration dikkat noktaları:

  • HasColumnType("nvarchar(max)") → PG'de hata verir! Provider değiştirirsen tüm migration'ları yeniden oluştur
  • UseSnakeCaseNamingConvention() eklediysen ilk migration'dan önce ekle, sonradan ekleme büyük rename migration'ı üretir
  • Extension'lar (uuid-ossp, pg_trgm, hstore) ilk migration'da CREATE EXTENSION ile ekle
  • PostgreSQL transactional DDL destekler → migration başarısız olursa otomatik ROLLBACK (SQL Server'da bu garanti değil!)

Production'a Migration Deploy Checklist

🚨 Bu adımları atlama. Yanlış migration production veritabanını geri dönüşümsüz bozabilir.

# Adım Komut / Aksiyon
1 Backup al DB snapshot veya pg_dump / .bak backup
2 SQL script üret dotnet ef migrations script --idempotent -o deploy.sql
3 Script'i incele DROP, ALTER TYPE, data loss riski var mı?
4 Staging'de çalıştır Aynı script'i staging DB'ye uygula, hata var mı?
5 Rollback planı yaz Geri alma script'i hazırla: dotnet ef migrations script <önceki> <hedef>
6 Maintenance window Gerekirse kısa downtime planla (rename, type change durumlarında)
7 Deploy et Script'i production'a uygula (sqlcmd / psql)
8 Doğrula SELECT * FROM __EFMigrationsHistory — son migration göründü mü?
# İdempotent script (zaten uygulanmış migration'ları atlar):
dotnet ef migrations script --idempotent -o deploy.sql

# Belirli bir migration'a kadar:
dotnet ef migrations script InitialCreate AddProductIndex -o partial.sql

# Rollback script (AddProductIndex'ten InitialCreate'e geri dön):
dotnet ef migrations script AddProductIndex InitialCreate -o rollback.sql

Asla production'da dotnet ef database update çalıştırma. Bu komut CI/CD veya local dev içindir. Production'da her zaman idempotent SQL script kullan ve DBA review'dan geçir.