EF Core migration'larını özelleştirmek, EF Core'un ürettiği Up ve Down metotlarını elle düzenlemek, araya migrationBuilder.Sql() ile ham SQL eklemek ya da migration altyapısını (geçmiş tablosu, proje düzeni, sağlayıcı) ihtiyacınıza göre ayarlamak demektir. Entity Framework Core çoğu zaman doğru migration üretir; ancak kolon yeniden adlandırma, veri dönüştürme, saklı yordam veya view yönetimi gibi durumlarda üretilen kodu değiştirmeniz gerekir, bazen de bu zorunludur.

Bu yazı özelleştirme tarafını kapsar: yeniden adlandırma tuzağı, InsertData/UpdateData/DeleteData ile sabit veri işlemleri, mevcut veriden yeni kolon doldurma, ham SQL ile veritabanı nesneleri, özel migration işlemleri, geçmiş tablosunu değiştirme, migration'ları ayrı projeye taşıma, birden fazla sağlayıcı ve takım çalışmasında dallanan migration'lar. Migration eklemenin temel akışı EF Core migration rehberinde, production'a uygulama SQL script ve bundle yazısında anlatılıyor; serinin tamamı için EF Core öğrenme rehberi sayfasına bakın.

Kolon yeniden adlandırma: en bilinen tuzak

Bir Customer entity'sindeki Name property'sini FullName olarak yeniden adlandırdığınızı düşünün. EF Core genellikle niyetin iki ayrı değişiklik (bir kolonu silmek ve yenisini eklemek) mi, yoksa bir yeniden adlandırma mı olduğunu bilemez. Belgelerdeki örnekte üretilen migration şöyledir:

csharp
migrationBuilder.DropColumn(
    name: "Name",
    table: "Customers");

migrationBuilder.AddColumn<string>(
    name: "FullName",
    table: "Customers",
    nullable: true);

Bu migration olduğu gibi uygulanırsa tüm müşteri adları kaybolur. Doğrusu, bu iki işlemi tek bir RenameColumn çağrısıyla değiştirmektir. Kolon verisi korunur, yalnızca adı değişir:

csharp
migrationBuilder.RenameColumn(
    name: "Name",
    table: "Customers",
    newName: "FullName");

Migration içinde veri işlemleri

Migration içinde veri işlemleri: Sabit değerler ve açık anahtarla belirlenen satırlar, Yeni değerlerin mevcut veritabanı verisinden hesaplanması, Tekrar kullanılabilir ve...

Migration'lar şemayı değiştirmenin yanında veri de taşıyabilir. Belgeler seçimi şu soruya bağlar: değerler migration'ı yazdığınız anda biliniyor mu?

  • Sabit değerler ve açık anahtarla belirlenen satırlar için InsertData, UpdateData ve DeleteData kullanın. EF Core bunları sağlayıcıya özgü SQL'e çevirdiği için script ve bundle üretirken de çalışırlar.
  • Yeni değerlerin mevcut veritabanı verisinden hesaplanması gerekiyorsa Sql kullanın. SQL sözdizimi sağlayıcıya göre değişebileceği için gerektiğinde MigrationBuilder.ActiveProvider üzerinden dallanın.
  • Tekrar kullanılabilir ve sağlayıcıya özgü SQL üretmesi gereken bir işlem için özel bir migration işlemi tanımlayın (aşağıda).

Sabit veri ekleme, güncelleme ve silme

csharp
migrationBuilder.InsertData(
    table: "Countries",
    columns: new[] { "CountryId", "Name" },
    values: new object[,]
    {
        { 1, "United States" },
        { 2, "Canada" }
    });

migrationBuilder.UpdateData(
    table: "Countries",
    keyColumn: "CountryId",
    keyValue: 1,
    column: "Name",
    value: "United States of America");

migrationBuilder.DeleteData(
    table: "Countries",
    keyColumn: "CountryId",
    keyValue: 2);

Her işlemin Down karşılığını düşünün: InsertData için aynı anahtarlarla DeleteData, UpdateData için önceki değerleri geri yazan bir güncelleme, geri alınabilir olması gereken bir DeleteData için ise silinen her değeri geri ekleyen InsertData. Bu işlemler veritabanının o anki durumunu sorgulamaz; davranış mevcut veriye bağlıysa Sql veya başlatma anında seed kullanmanız gerekir. Modelde HasData ile tanımlanan veri de migration eklenirken tam olarak bu üç işleme çevrilir; seed seçeneklerinin karşılaştırması data seeding yazısında var.

Mevcut veriyi dönüştürmek: dört adımlı kalıp

FirstName ve LastName kolonlarını tek bir FullName kolonuyla değiştirmek istediğinizi düşünün. Belgeler, hedef kolon dolana kadar kaynak veriyi korumak için şu sırayı önerir: hedef kolonu nullable olarak ekleyin, mevcut kolonlardan doldurun, uygunsa zorunlu yapın, en son kaynak kolonları silin. Aşağıdaki örnek bu sırayı SQL Server ve SQLite için uygular; desteklenmeyen bir sağlayıcıda ise açıkça hata verir:

csharp
migrationBuilder.AddColumn<string>(
    name: "FullName",
    table: "Customers",
    nullable: true);

if (migrationBuilder.ActiveProvider == "Microsoft.EntityFrameworkCore.SqlServer")
{
    migrationBuilder.Sql(
        """
        UPDATE [Customers]
        SET [FullName] = [FirstName] + N' ' + [LastName];
        """);
}
else if (migrationBuilder.ActiveProvider == "Microsoft.EntityFrameworkCore.Sqlite")
{
    migrationBuilder.Sql(
        """
        UPDATE "Customers"
        SET "FullName" = "FirstName" || ' ' || "LastName";
        """);
}
else
{
    throw new NotSupportedException(
        $"Data migration is not implemented for provider {migrationBuilder.ActiveProvider}.");
}

migrationBuilder.AlterColumn<string>(
    name: "FullName",
    table: "Customers",
    nullable: false,
    oldClrType: typeof(string),
    oldNullable: true);

migrationBuilder.DropColumn(
    name: "FirstName",
    table: "Customers");

migrationBuilder.DropColumn(
    name: "LastName",
    table: "Customers");

Bu kalıptaki üç ayrıntı önemlidir. Birincisi, uygulamanın desteklediği her sağlayıcı için bir dal ekleyin; bilinmeyen sağlayıcıda istisna fırlatmak, eksik bir migration'ı sessizce uygulamaktan daha güvenlidir. İkincisi, SQL'i güvenilmeyen değerlerden oluşturmayın: migration SQL'i şema değiştirme yetkileriyle çalışır. Üçüncüsü, bazı dönüşümler bilgi kaybı olmadan geri alınamaz. Örneğin birleştirilmiş bir ad, ad ve soyada her zaman doğru ayrılamaz. Belgeler, Down metodunu yalnızca orijinal değerler güvenle yeniden kurulabiliyorsa yazmanızı, aksi hâlde açıkça hata vermenizi ve geri dönüş prosedürüne yedekten geri yüklemeyi eklemenizi önerir.

Ham SQL ile EF Core'un bilmediği nesneler

Ham SQL, EF Core'un haberdar olmadığı veritabanı nesnelerini yönetmek için de kullanılabilir. Bunun için modelde hiçbir değişiklik yapmadan bir migration eklersiniz; boş bir migration üretilir ve siz onu ham SQL işlemleriyle doldurursunuz. Belgelerdeki örnek SQL Server'da bir saklı yordam oluşturur:

bash
dotnet ef migrations add AddGetFullNameProcedure
csharp
migrationBuilder.Sql(
@"
    EXEC ('CREATE PROCEDURE getFullName
        @LastName nvarchar(50),
        @FirstName nvarchar(50)
    AS
        SELECT @LastName + @FirstName;')");

Buradaki EXEC bilinçli bir seçimdir: bir ifadenin SQL batch'inde ilk veya tek ifade olması gerektiğinde kullanılır. Ayrıca idempotent migration script'lerinde, başvurulan kolonlar o an tabloda bulunmadığı için ortaya çıkabilen ayrıştırıcı hatalarını aşmak için de işe yarar. Bu yöntemle saklı yordamlar, tam metin arama, fonksiyonlar, tetikleyiciler (trigger) ve view'lar gibi veritabanının her yönünü migration geçmişinin parçası hâline getirebilirsiniz. Böylece yeni bir ortam kurduğunuzda bu nesneler de tablolarla birlikte oluşur. Bir sınırlamayı da bilin: bu nesneler EF modelinin parçası değildir. SQLite sağlayıcısı desteklenmeyen bazı şema işlemlerini tabloyu yeniden kurarak (rebuild) yapar, ama belgelere göre rebuild yalnızca modelin parçası olan nesneler için mümkündür; migration içinde elle oluşturulmuş bir nesne söz konusuysa yine NotSupportedException fırlatılır.

Transaction ayrıntısına da dikkat edin. Çoğu durumda EF Core migration uygularken her migration'ı kendi transaction'ı içine alır. Bazı veritabanlarında bazı işlemler transaction içinde yapılamaz; bu durumlar için migrationBuilder.Sql çağrısına suppressTransaction: true geçerek transaction'dan çıkabilirsiniz. EF Core 9 bekleyen tüm migration'ları tek transaction'la sarmaya başlamıştı; belgelere göre bu davranış EF Core 10'da geri alındı.

Özel migration işlemleri

MigrationBuilder API'si çok sayıda işlem sunar ama her şeyi kapsamaz; neyse ki genişletilebilir. Belgeler iki yol gösterir ve örnek olarak bir veritabanı kullanıcısı oluşturan migrationBuilder.CreateUser(...) işlemini kullanır. İlk ve en kolay yol, Sql() çağıran bir extension metottur. Birden fazla sağlayıcıyı desteklemeniz gerekiyorsa ActiveProvider ile dallanırsınız:

csharp
public static OperationBuilder<SqlOperation> CreateUser(
    this MigrationBuilder migrationBuilder,
    string name,
    string password)
{
    switch (migrationBuilder.ActiveProvider)
    {
        case "Npgsql.EntityFrameworkCore.PostgreSQL":
            return migrationBuilder
                .Sql($"CREATE USER {name} WITH PASSWORD '{password}';");

        case "Microsoft.EntityFrameworkCore.SqlServer":
            return migrationBuilder
                .Sql($"CREATE USER {name} WITH PASSWORD = '{password}';");
    }

    throw new Exception("Unexpected provider.");
}

Bu yaklaşım yalnızca özel işleminizin uygulanacağı her sağlayıcıyı biliyorsanız çalışır. Ayrıca örnekteki değerler doğrudan SQL metnine yerleştiriliyor; bu yüzden yalnızca migration'ı yazan kişinin kontrol ettiği sabit değerlerle kullanılmalıdır. Belgelerin genel kuralı burada da geçerlidir: migration SQL'ini güvenilmeyen değerlerden kurmayın.

İkinci yol, işlemi SQL'den ayırmaktır: kendi MigrationOperation sınıfınızı tanımlarsınız, extension metot bu nesneyi MigrationBuilder.Operations listesine ekler ve SQL'i her sağlayıcının IMigrationsSqlGenerator servisi üretir. Belgelerdeki örnek SQL Server'ın üreticisinden türeyen bir sınıf yazar, Generate metodunu ezer ve kimlik ile parola değerlerini sağlayıcının yardımcılarıyla (DelimitIdentifier, GenerateSqlLiteral) güvenli biçimde yazar. Son adım, varsayılan servisi değiştirmektir:

csharp
public class CreateUserOperation : MigrationOperation
{
    public string Name { get; set; }
    public string Password { get; set; }
}

protected override void OnConfiguring(DbContextOptionsBuilder options)
    => options
        .UseSqlServer(_connectionString)
        .ReplaceService<IMigrationsSqlGenerator, MyMigrationsSqlGenerator>();

Bu yol daha fazla kod ister ama iki kazanç sağlar: migration kodunuz sağlayıcıdan bağımsız kalır ve SQL üretimi tek bir yerde, sağlayıcının kendi tırnaklama ve literal üretme kurallarıyla yapılır. Tek bir proje ve tek bir veritabanıyla çalışıyorsanız çoğu zaman Sql() extension'ı yeterlidir.

Geçmiş tablosunu özelleştirmek

EF Core uygulanan migration'ları varsayılan olarak __EFMigrationsHistory adlı tabloda tutar. Tablo adını ve şemasını sağlayıcı seçeneklerindeki MigrationsHistoryTable() ile değiştirebilirsiniz. Daha ileri değişiklikler, örneğin MigrationId kolonunun adını değiştirmek, sağlayıcıya özgü IHistoryRepository servisini ezip ReplaceService ile değiştirerek yapılır:

csharp
protected override void OnConfiguring(DbContextOptionsBuilder options)
    => options.UseSqlServer(
        _connectionString,
        x => x.MigrationsHistoryTable("__MyMigrationsHistory", "mySchema"));

Migration'ları ayrı bir projeye taşımak

Migration'ları ayrı bir projeye taşımak: WebApplication1.Data, WebApplication1.Migrations, WebApplication1

Migration'ları DbContext'i içeren projeden farklı bir projede tutabilirsiniz. Belgeler bunu özellikle uygulama projesi platforma özgü olduğunda (WinUI, .NET MAUI, Blazor WebAssembly, Azure Functions) veya belirli bir çalışma zamanı kimliğini (RID) hedeflediğinde önerir; birden fazla migration seti tutmak için de kullanılır. Örnek düzen üç projeden oluşur:

ProjeSorumlulukReferanslar
WebApplication1.DataDbContext ve entity tipleriEF Core sağlayıcısı
WebApplication1.MigrationsMigration'lar, model snapshot ve design-time context oluşturmaData projesi, sağlayıcı, Microsoft.EntityFrameworkCore.Design
WebApplication1Uygulamayı çalıştırırData projesi ve migrations projesi

Migrations projesi bir sınıf kütüphanesidir; Data projesine referans verir, sağlayıcıyı ve geliştirme bağımlılığı olarak işaretlenmiş Microsoft.EntityFrameworkCore.Design paketini içerir. İçine bir IDesignTimeDbContextFactory<TContext> uygulaması konur; böylece araçlar context'i uygulama projesini çalıştırmadan oluşturabilir (ayrıntı: design-time DbContext factory). Çalışma zamanında context kaydedilirken migration assembly'si belirtilir:

csharp
services.AddDbContext<ApplicationDbContext>(
    options =>
        options.UseSqlServer(
            Configuration.GetConnectionString("DefaultConnection"),
            x => x.MigrationsAssembly("WebApplication1.Migrations")));

Uygulama projesinin migrations projesine referans vermesi yalnızca uygulama çalışma zamanında migration'ları keşfediyor veya uyguluyorsa (örneğin Migrate çağırıyorsa) gerekir; migration'lar yalnızca bir dağıtım çıktısıyla uygulanıyorsa bu referans gerekmez. Data projesi ise migrations projesine asla referans vermemelidir, aksi hâlde döngüsel bağımlılık oluşur. Araçları çalıştırırken migrations projesini hem hedef hem başlangıç projesi olarak verirsiniz; bu, araçların uygulama başlangıç kodunu çalıştırmasını da engeller:

bash
dotnet ef migrations add NewMigration \
    --project WebApplication1.Migrations \
    --startup-project WebApplication1.Migrations

Belgeler bir de işlemci mimarisi uyarısı yapar: araçları çalıştıran süreç tüm tasarım zamanı assembly'lerini yükleyebilmelidir; 64 bit bir süreç yalnızca x86 için derlenmiş bir başlangıç assembly'sini yükleyemez. Bu yüzden AnyCPU bir migrations projesi tercih edilir. Tasarım zamanı mimarisi dağıtım hedefinden ayrıdır; bundle üretirken hedef RID'yi --target-runtime ile ayrıca belirtirsiniz.

Birden fazla veritabanı sağlayıcısı

EF Core araçları migration'ı yalnızca etkin sağlayıcı için üretir. Aynı modeli hem SQL Server hem SQLite ile kullanmak istiyorsanız her sağlayıcı için ayrı bir migration seti tutar ve her model değişikliğinde her sete bir migration eklersiniz. En basit yol, sağlayıcı başına bir context tipi kullanmaktır:

csharp
class SqliteBlogContext : BlogContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder options)
        => options.UseSqlite("Data Source=my.db");
}
bash
dotnet ef migrations add InitialCreate --context BlogContext --output-dir Migrations/SqlServerMigrations
dotnet ef migrations add InitialCreate --context SqliteBlogContext --output-dir Migrations/SqliteMigrations

Sonraki migration'larda klasörü yeniden belirtmeniz gerekmez, çünkü yeni migration son migration'ın yanına oluşturulur. Tek bir context tipiyle de çalışılabilir; bu durumda migration'ların ayrı assembly'lere taşınması gerekir ve sağlayıcı, araçlara -- sonrası verilen bir argümanla (örneğin -- --provider Sqlite) uygulama yapılandırmasından seçilir. Bir sağlayıcı için üretilmiş migration'ı başka bir sağlayıcıyla uygulamak desteklenmeyen bir senaryodur ve EF Core 9 sonrası bekleyen model değişikliği hatasına yol açabilir; bkz. PendingModelChangesWarning. Sağlayıcıların kendi farkları veritabanı sağlayıcıları yazısında anlatılıyor.

Takım ortamında dallanan migration'lar

Migration'lar yalnızca SQL script değildir; her biri o andaki modelin anlık görüntüsünü de taşır. İki geliştirici aynı anda ayrı dallarda migration ürettiğinde, sonra birleştirilen dalın migration'ı diğer dalın değişikliklerini içermeyen bir snapshot'a sahip olur ve bu sonraki migration'larda bozulmalara yol açabilir. Belgeler önceden koordinasyon yapmayı ve mümkünse aynı anda birden çok dalda migration üzerinde çalışmamayı önerir. Paralel migration'ları dosyaları sıralayarak veya yeniden adlandırarak çözmeye çalışmayın; sonraki migration yine diğer dalın değişikliklerini içermeyen meta veri taşır.

Dallanma fark edildiğinde önerilen çözüm, kendi migration'ınızı yeniden oluşturmaktır: birleştirmeyi iptal edip birleştirme öncesi çalışma dizinine dönün, model değişikliklerinizi koruyarak yalnızca üretilen migration dosyalarını kaldırın ve snapshot'ı migration öncesi hâline getirin (bunu kaynak kontrolüyle yapabilirsiniz), takım arkadaşınızın değişikliklerini birleştirin ve migration'ınızı birleştirilmiş snapshot'ın üzerine yeniden ekleyin. Geçersiz bir sıralama zaten birleştirilmişse migrations remove çalıştırmayın; komut önceki migration'ın meta verisindeki modeli geri yükler ve bu model diğer dalın değişikliklerini içermeyebilir.

Kaynak kontrolünde bir commit'i geri almak hiçbir veritabanını değiştirmez. Migration kodunu kaldırmadan önce, migration paylaşılan bir veritabanına uygulanmadıysa önce migration'ı kaldırıp sonra model değişikliklerini geri alın; uygulandıysa migration kodu hâlâ elinizdeyken veritabanını önceki migration'a geri alın ya da düzeltici yeni bir migration yayımlayın.

Sık yapılan hatalar

Sık yapılan hatalar: Yeniden adlandırmayı silme ve ekleme olarak bırakmak., Migration içinde güncel entity sınıflarını veya DbContext kullanarak veri..., ActiveProvider...
  • Yeniden adlandırmayı silme ve ekleme olarak bırakmak.
  • Migration içinde güncel entity sınıflarını veya DbContext kullanarak veri taşımak.
  • ActiveProvider dallanmasında bilinmeyen sağlayıcı için sessizce hiçbir şey yapmamak.
  • Geri alınamayan bir veri dönüşümü için yanıltıcı bir Down yazmak.
  • Geçmiş tablosunun adını uygulanmış bir veritabanında tabloyu taşımadan değiştirmek.
  • Paralel migration'ları dosya adlarını değiştirerek sıraya sokmak.

Sık Sorulan Sorular

Migration dosyasını elle düzenlemek güvenli mi?

Evet; belgeler üretilen kodu her zaman incelemenizi ve gerektiğinde değiştirmenizi önerir. Snapshot dosyasını ise elle düzenlemeyin; o, sonraki migration'ın neyi değiştireceğini belirleyen model görüntüsüdür.

Saklı yordamları migration ile yönetebilir miyim?

Evet. Model değişikliği olmadan boş bir migration ekleyip migrationBuilder.Sql() ile saklı yordam, view, fonksiyon veya trigger oluşturabilirsiniz. Down metoduna nesneyi kaldıran SQL'i yazmayı unutmayın.

Migration'ı transaction dışında çalıştırmam gerekiyor, ne yapmalıyım?

İlgili migrationBuilder.Sql çağrısına suppressTransaction: true geçin. Bu, transaction içinde yapılamayan işlemler için öngörülmüş bir seçenektir.

PostgreSQL için migration'da hangi sağlayıcı adını kullanmalıyım?

Belgelerdeki özel işlem örneği PostgreSQL için Npgsql.EntityFrameworkCore.PostgreSQL, SQL Server için Microsoft.EntityFrameworkCore.SqlServer değerini kullanır. Üçüncü taraf sağlayıcıların ayrıntıları kendi belgelerindedir.

Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.

İlgili içerikler: EF Core öğrenme rehberi · EF Core migration rehberi · Migration'ı production'a uygulama · Data seeding · Design-time DbContext factory · Veritabanı sağlayıcıları