Migration pada Entity Framework Core adalah mekanisme untuk melacak dan menerapkan perubahan skema database secara terstruktur dan dapat diulang, memungkinkan struktur database berevolusi seiring perubahan model C# tanpa perlu mengubah database secara manual lewat script SQL terpisah.
Setiap migration merepresentasikan satu set perubahan skema, seperti penambahan tabel baru atau kolom tambahan, yang disimpan sebagai file C# berisi instruksi Up (menerapkan perubahan) dan Down (membatalkan perubahan), memungkinkan riwayat perubahan database terlacak jelas seperti riwayat commit pada version control.
Artikel ini membahas cara membuat dan menerapkan migration, serta seeding data untuk mengisi data awal yang dibutuhkan aplikasi, seperti data master atau akun administrator default, secara otomatis saat database pertama kali dibuat.
Membuat Migration Pertama
Perintah dotnet ef migrations add membuat file migration baru
Menerapkan Migration ke Database
dotnet ef database update menerapkan migration ke database sesungguhnya
Menambahkan Migration untuk Perubahan Skema Baru
Setiap perubahan model membutuhkan migration baru
Seeding Data Awal dengan HasData
HasData mendefinisikan data awal yang disertakan dalam migration
Idempotent Migration dan Menangani Migration yang Sudah Terlanjur Jalan
Membuat script migration idempotent untuk deployment ke server production
Pada tim dengan banyak developer, migration EF Core sering dibuat paralel oleh beberapa orang sehingga urutan penerapannya di database production perlu dipastikan aman walau dijalankan berulang kali atau di server yang sudah punya sebagian migration ter-apply. Perintah dotnet ef migrations script --idempotent menghasilkan file SQL yang membungkus setiap perubahan dengan pengecekan pada tabel __EFMigrationsHistory, sehingga migration yang sudah pernah dijalankan otomatis dilewati tanpa menimbulkan error duplikasi kolom atau tabel.
Script idempotent ini jauh lebih aman dipakai pada pipeline CI/CD dibandingkan menjalankan dotnet ef database update langsung di server production, karena tim operasional bisa meninjau isi SQL sebelum dieksekusi, dan script yang sama bisa dijalankan ulang tanpa risiko merusak skema yang sudah ada jika deployment gagal di tengah jalan dan perlu diulang.
Situasi yang kerap membingungkan adalah ketika perubahan skema sudah terlanjur dieksekusi manual lewat SQL Server Management Studio tanpa melalui migration EF Core, sehingga skema database dan riwayat migration menjadi tidak sinkron. Pada kasus ini, tabel __EFMigrationsHistory bisa diperbarui secara manual untuk menandai migration tertentu sudah diterapkan, atau menggunakan dotnet ef database update dengan menunjuk migration yang sesuai agar EF Core tidak mencoba menjalankan ulang DDL yang sudah ada.
Seeding Data: HasData di Migration vs Seed Manual Saat Startup
HasData menghasilkan migration tetap; seed manual berjalan setiap aplikasi start
HasData yang dipanggil di dalam OnModelCreating membuat EF Core menghasilkan baris INSERT eksplisit di dalam file migration itu sendiri, sehingga data referensi seperti daftar role atau kategori tetap konsisten di semua environment karena ikut tercatat sebagai bagian dari riwayat skema database. Pendekatan ini cocok untuk data statis yang jarang berubah dan nilainya sudah pasti sejak awal pengembangan, seperti master data role atau status pesanan. Primary key pada data HasData harus ditentukan secara eksplisit, karena EF Core tidak bisa mengandalkan nilai identity otomatis saat membuat skrip migration.
Untuk data yang membutuhkan logika kondisional, seperti mengecek apakah tabel sudah berisi data sebelum insert, atau nilai yang bergantung pada hasil pemanggilan service lain, pendekatan seed manual yang dijalankan di Program.cs lewat scope sementara (CreateScope) lebih fleksibel dibanding HasData yang sifatnya statis dan dievaluasi saat compile time migration. Scope manual ini diperlukan karena DbContext bersifat Scoped sedangkan kode startup di Program.cs berjalan di luar konteks request. Pendekatan ini juga memungkinkan pemanggilan service lain seperti IPasswordHasher sebelum data benar-benar disimpan ke database.
Kombinasi keduanya sering dipakai dalam satu project: HasData untuk master data yang benar-benar tetap seperti daftar role, dan seed manual untuk data awal yang lebih dinamis seperti akun admin default yang passwordnya perlu di-hash terlebih dahulu sebelum disimpan. Memilih pendekatan yang salah, misalnya memakai HasData untuk data yang sering berubah, akan memaksa pembuatan migration baru setiap kali nilai seed tersebut perlu diperbarui. Tim yang paham perbedaan ini bisa menghindari migration yang menumpuk hanya untuk mengubah data referensi yang sebetulnya lebih cocok dikelola lewat seeding manual.
Menambahkan Concurrency Token agar Update Bersamaan Tidak Saling Menimpa
Tanpa concurrency token, dua proses yang membaca lalu menyimpan baris data yang sama bisa saling menimpa perubahan satu sama lain tanpa ada peringatan apa pun, dan proses yang menyimpan belakangan akan menang begitu saja. Menambahkan kolom RowVersion bertipe timestamp membuat EF Core secara otomatis memeriksa apakah data masih sama persis seperti saat terakhir dibaca sebelum benar-benar menjalankan perintah UPDATE ke database.
Migration yang menambahkan kolom RowVersion ini perlu dijalankan dengan hati-hati pada tabel yang sudah berisi data produksi, karena nilai awal kolom tersebut akan diisi otomatis oleh mesin database saat migration dijalankan. Saat DbUpdateConcurrencyException muncul, aplikasi perlu menangani kasus tersebut dengan baik, misalnya meminta pengguna memuat ulang data terbaru sebelum mencoba menyimpan perubahannya sekali lagi.
Kesalahan Umum yang Sering Terjadi
Mengedit File Migration yang Sudah Diterapkan ke Production
Mengubah file migration yang sudah diterapkan ke database production bisa menyebabkan ketidaksesuaian antara riwayat migration yang tercatat EF Core dengan skema database sesungguhnya; perubahan lebih lanjut sebaiknya selalu dibuat sebagai migration baru.
Lupa Menjalankan Database Update Setelah Membuat Migration
Membuat file migration dengan migrations add saja tidak mengubah database sama sekali; perintah database update tetap wajib dijalankan agar perubahan skema benar-benar diterapkan ke database sesungguhnya.
Menghapus Migration Lama Tanpa Memahami Konsekuensinya
Menghapus file migration lama secara sembarangan bisa merusak riwayat perubahan skema yang dibutuhkan EF Core untuk menentukan perubahan apa saja yang perlu diterapkan pada database yang belum ter-update ke versi terbaru.
Kapan Pendekatan Ini Tidak Cocok Dipakai
Migration berbasis kode memberikan jejak audit yang jelas dan bisa diintegrasikan dengan version control, memudahkan tim melacak evolusi skema database seiring waktu bersamaan dengan perubahan kode aplikasi.
Namun untuk database yang sangat besar dan kompleks dengan banyak constraint khusus, beberapa tim memilih mengelola skema database secara terpisah dengan tool migrasi database khusus (database-first) dibanding mengandalkan migration otomatis EF Core (code-first) yang terkadang kurang presisi untuk kasus skema yang sangat spesifik.
Ringkasan
Migration EF Core memberikan cara terstruktur untuk mengevolusikan skema database seiring perubahan model aplikasi, dengan HasData sebagai pelengkap untuk seeding data awal yang dibutuhkan. Selalu buat migration baru untuk setiap perubahan skema, dan hindari mengedit migration yang sudah diterapkan ke environment production.
Seri Belajar .NET Core:
← Sebelumnya: Entity Framework Core untuk Pemula
→ Selanjutnya: Relasi One to Many dan Many to Many di EF Core