EF Core adalah ORM (object-relational mapping) resmi untuk .NET yang menerjemahkan operasi pada objek C# menjadi pernyataan SQL, sehingga data dapat disimpan dan dibaca tanpa menulis SQL secara manual untuk setiap kebutuhan. Topik ini membahas konsep ORM, tiga blok penyusun utama, yaitu kelas entity, DbContext, dan DbSet, operasi dasar CRUD, cara change tracker melacak perubahan, serta pengenalan migration untuk mencatat perubahan skema basis data. Hasil akhirnya berupa satu program berkas tunggal berbasis SQLite yang menampilkan keluaran CRUD secara deterministik, ditutup dengan langkah migration dari sisi command line.
Apa itu ORM dan EF Core#
ORM adalah pustaka yang memetakan baris pada tabel basis data menjadi objek, dan sebaliknya. Dengan pemetaan itu, program mengolah data melalui kelas dan properti, sedangkan penerjemahannya menjadi pernyataan SQL menjadi tugas pustaka tersebut. Penulisan SQL manual untuk operasi sehari-hari menjadi tidak diperlukan, meskipun SQL tetap dapat ditulis bila querynya memang rumit.
EF Core memenuhi peran tersebut untuk ekosistem .NET. Cara kerjanya bertumpu pada provider, yaitu komponen yang menerjemahkan perintah EF Core menjadi dialek SQL tertentu, misalnya SQL Server, PostgreSQL, atau SQLite. Query ditulis dengan LINQ sehingga pemfilteran dan pengurutan data menggunakan sintaks yang sama seperti saat mengolah kumpulan objek di memori, tetapi eksekusinya dipindahkan ke basis data.
Topik ini menggunakan provider SQLite karena basis datanya tersimpan dalam satu berkas dan berjalan tanpa server, sehingga cocok untuk latihan dan pengujian di lingkungan yang minim. Seluruh contoh berjalan pada .NET 10 dengan paket Microsoft.EntityFrameworkCore.Sqlite, dan polanya sama untuk provider lain karena API yang digunakan tidak berubah.
DbContext, DbSet, dan kelas entity#
Ketiga blok penyusun EF Core berupa kelas entity, kelas turunan DbContext, dan properti DbSet<T>. Kelas entity adalah kelas biasa yang propertinya mewakili kolom tabel, sedangkan setiap objeknya mewakili satu baris. EF Core membentuk skema dari kelas ini lewat konvensi, misalnya properti bernama Id pada kelas Buku otomatis menjadi kunci utama (primary key) yang nilainya diberi nomor berurutan oleh basis data. Kelas turunan DbContext berperan sebagai sesi kerja antara program dan basis data, sedangkan setiap properti DbSet<T> menyatakan satu himpunan entity, yang dalam basis data relasional berarti satu tabel.
Metode OnConfiguring pada DbContext adalah tempat memilih basis data yang digunakan. Pemanggilan UseSqlite dengan opsi Data Source mengarahkan seluruh operasi context ke berkas SQLite tersebut. Potongan berikut memperlihatkan ketiga blok itu untuk aplikasi perpustakaan sederhana yang digunakan sepanjang topik.
// tidak-dijalankan
class Buku
{
public int Id { get; set; }
public string Judul { get; set; } = "";
public int Tahun { get; set; }
}
class PerpustakaanContext : DbContext
{
public const string BerkasDb = "perpustakaan.db";
public DbSet<Buku> Buku => Set<Buku>();
protected override void OnConfiguring(DbContextOptionsBuilder options)
=> options.UseSqlite($"Data Source={BerkasDb}");
}Kelas Buku tidak mewarisi kelas apa pun dan tidak membawa atribut khusus, sehingga kelas ini tetap dapat digunakan tanpa EF Core sekalipun. Kelas PerpustakaanContext mewarisi DbContext, menampung satu DbSet<Buku>, dan properti Set<Buku>() mengambil himpunan entity milik tipe tersebut dari context. Konstanta BerkasDb menyimpan nama berkas agar nilai yang sama digunakan oleh penghapusan di awal program dan oleh koneksi di bagian bawah berkas.
Operasi CRUD dasar#
CRUD adalah singkatan dari create, read, update, dan delete, yaitu empat operasi dasar penyimpanan data. Pada EF Core, Add mengirim INSERT, Find dan query LINQ mengirim SELECT, perubahan properti mengirim UPDATE, sedangkan Remove mengirim DELETE. Keempatnya hanya menyiapkan perubahan di dalam context, dan pernyataan SQL baru benar-benar dikirim ketika SaveChanges dipanggil.
Program berikut menjalankan empat operasi tersebut secara berurutan. Arahan #:package [email protected] memuat EF Core beserta provider SQLite-nya, sedangkan arahan #:property PublishAot=false mematikan Native AOT yang menjadi default eksekusi aplikasi berkas tunggal pada .NET 10 karena pembuatan model EF Core memerlukan refleksi saat runtime. Di awal, program menghapus berkas basis data beserta dua berkas sampingan yang mungkin ditinggalkan SQLite agar setiap eksekusi dimulai dari keadaan kosong dan ID selalu berurutan dari 1. Metode EnsureCreated kemudian membuat berkas basis data beserta tabelnya langsung dari model tanpa migration.
#:package Microsoft.EntityFrameworkCore.Sqlite@10.0.14
#:property PublishAot=false
// Program: CRUD dasar dengan EF Core dan basis data SQLite
using Microsoft.EntityFrameworkCore;
// hapus sisa basis data dari eksekusi sebelumnya agar keluaran selalu sama
foreach (var nama in new[] { PerpustakaanContext.BerkasDb,
PerpustakaanContext.BerkasDb + "-wal",
PerpustakaanContext.BerkasDb + "-shm" })
{
try { File.Delete(nama); } catch (IOException) { }
}
using var db = new PerpustakaanContext();
db.Database.EnsureCreated();
// CREATE: Add menandai objek baru berstatus Added, SaveChanges mengirim INSERT
var bukuA = new Buku { Judul = "Algoritma Dasar", Tahun = 2023 };
var bukuB = new Buku { Judul = "Dasar Jaringan", Tahun = 2024 };
db.Buku.Add(bukuA);
db.Buku.Add(bukuB);
Console.WriteLine($"Status sebelum disimpan: {db.Entry(bukuA).State}");
db.SaveChanges();
Console.WriteLine($"Status sesudah disimpan: {db.Entry(bukuA).State}");
Console.WriteLine($"ID yang diberikan: {bukuA.Id}, {bukuB.Id}");
// READ: Find mencari berdasarkan kunci utama
var hasil = db.Buku.Find(bukuB.Id);
Console.WriteLine($"Find({bukuB.Id}): {hasil!.Judul} ({hasil.Tahun})");
// READ: query LINQ diterjemahkan menjadi SQL dan dijalankan di basis data
Console.WriteLine("Buku dengan tahun < 2024:");
foreach (var b in db.Buku.Where(b => b.Tahun < 2024).OrderBy(b => b.Judul))
{
Console.WriteLine($" - {b.Judul} ({b.Tahun})");
}
// UPDATE: cukup ubah propertinya, SaveChanges mengirim UPDATE
hasil.Tahun = 2025;
Console.WriteLine($"Status sesudah tahun diubah: {db.Entry(hasil).State}");
db.SaveChanges();
Console.WriteLine($"Status sesudah disimpan ulang: {db.Entry(hasil).State}");
// DELETE: Remove menandai berstatus Deleted, SaveChanges mengirim DELETE
db.Buku.Remove(bukuA);
Console.WriteLine($"Status sesudah Remove: {db.Entry(bukuA).State}");
db.SaveChanges();
Console.WriteLine($"Sisa buku: {db.Buku.Count()}");
var sisa = db.Buku.Single();
Console.WriteLine($"Sisa: {sisa.Judul} ({sisa.Tahun})");
// kelas entity dan kelas DbContext dinyatakan setelah pernyataan top-level
class Buku
{
public int Id { get; set; }
public string Judul { get; set; } = "";
public int Tahun { get; set; }
}
class PerpustakaanContext : DbContext
{
// berkas basis data tetap di folder kerja agar mudah diperiksa
public const string BerkasDb = "perpustakaan.db";
public DbSet<Buku> Buku => Set<Buku>();
protected override void OnConfiguring(DbContextOptionsBuilder options)
=> options.UseSqlite($"Data Source={BerkasDb}");
}Jalankan program dari folder topik dengan perintah berikut.
# aktifkan .NET yang terpasang di sandbox
export DOTNET_ROOT=/home/kal/.dotnet PATH=/home/kal/.dotnet:$PATH DOTNET_CLI_TELEMETRY_OPTOUT=1 DOTNET_NOLOGO=1
# jalankan program dari folder topik
dotnet run --file Program.csStatus sebelum disimpan: Added
Status sesudah disimpan: Unchanged
ID yang diberikan: 1, 2
Find(2): Dasar Jaringan (2024)
Buku dengan tahun < 2024:
- Algoritma Dasar (2023)
Status sesudah tahun diubah: Modified
Status sesudah disimpan ulang: Unchanged
Status sesudah Remove: Deleted
Sisa buku: 1
Sisa: Dasar Jaringan (2025)Keluaran tersebut memperlihatkan siklus CRUD yang lengkap. Dua panggilan Add menandai kedua objek berstatus Added, kemudian SaveChanges mengirim dua INSERT dan memberi ID 1 serta 2 sesuai urutan penyisipan. Metode Find(2) mengembalikan buku kedua, sedangkan query LINQ berantai Where dan OrderBy diterjemahkan menjadi SQL sehingga penyaringannya terjadi di basis data, bukan di memori. Penugasan hasil.Tahun = 2025 berupa pernyataan properti biasa tanpa panggilan metode apa pun, dan dua baris terakhir menunjukkan bahwa UPDATE dan DELETE benar-benar tersimpan ke berkas basis data.
Cara change tracker melacak perubahan#
Change tracker adalah komponen di dalam DbContext yang mencatat setiap entity yang dibaca atau didaftarkan selama context hidup, lengkap dengan statusnya. Status sebuah entity berupa nilai enum EntityState, yaitu Added, Unchanged, Modified, Deleted, atau Detached. Entity yang baru didaftarkan lewat Add berstatus Added, entity yang dibaca dari basis data berstatus Unchanged, dan perubahan properti memindahkan statusnya ke Modified.
Ketika SaveChanges dipanggil, change tracker membandingkan nilai properti saat ini dengan salinan yang diambil ketika entity dibaca, lalu perbedaannya diubah menjadi pernyataan SQL yang sesuai. Entity berstatus Added menjadi INSERT, Modified menjadi UPDATE yang hanya menyertakan kolom yang berubah, dan Deleted menjadi DELETE. Inilah alasan pembaruan pada demo tidak membutuhkan metode khusus, karena penugasan hasil.Tahun = 2025 sudah dicatat oleh tracker. Metode Find juga menghormati tracker, yaitu entity yang sudah dilacak dikembalikan langsung dari memori tanpa mengirim query ke basis data.
Status pada keluaran demo mengikuti alur itu dengan tepat, yaitu Added sebelum SaveChanges, kembali Unchanged sesudahnya, naik ke Modified ketika properti diubah, dan berakhir Deleted ketika Remove dipanggil. Pemantauan seperti ini dilakukan lewat db.Entry(objek).State, sehingga program yang tidak menyimpan sebagaimana mestinya dapat diperiksa statusnya per objek.
Pengenalan migration#
EnsureCreated memang cepat, tetapi skema yang dibuatnya tidak meninggalkan catatan apa pun. Untuk aplikasi yang skemanya berkembang, EF Core menyediakan migration, yaitu berkas C# yang dihasilkan alat untuk menjelaskan satu langkah perubahan skema, misalnya menambah tabel, kolom, atau indeks. Setiap migration memuat metode Up yang menerapkan perubahan dan Down yang mengembalikannya, sedangkan tabel __EFMigrationsHistory pada basis data mencatat migration mana yang sudah diterapkan.
Alur kerjanya dimulai dengan memasang alat dotnet-ef sekali pada mesin, kemudian dua perintah dipanggil pada folder proyek biasa yang memiliki berkas csproj. Perintah migrations add membaca model lalu menghasilkan berkas migration, sedangkan perintah database update menerapkan berkas tersebut ke basis data.
# tidak-dijalankan
# pasang alat dotnet-ef sekali pada mesin
dotnet tool install --global dotnet-ef
# buat migration pertama bernama Inisial
dotnet ef migrations add Inisial
# terapkan migration untuk membuat atau memperbarui skema basis data
dotnet ef database updatePerintah migrations add Inisial menghasilkan folder Migrations yang berisi kelas migration beserta model snapshot, yaitu potret lengkap skema pada saat migration dibuat. Perintah database update kemudian menerapkan seluruh migration yang belum berjalan sesuai urutannya, sehingga skema basis data mengikuti model secara bertahap dan setiap langkahnya tetap tercatat. Bila model berubah lagi, satu migrations add dan satu database update sudah cukup untuk menyelaraskan basis data yang sudah berisi data.
Kesalahan umum#
- Menjalankan EF Core pada aplikasi berkas tunggal tanpa
#:property PublishAot=false. Eksekusi berkas tunggal menggunakan Native AOT secara default, sedangkan pembuatan model EF Core memerlukan refleksi, sehingga program berhenti dengan errorInvalidOperationExceptionberisi pesanModel building is not supported when publishing with NativeAOT. Perbaikannya adalah menambahkan arahan tersebut di bagian atas berkas. - Lupa memanggil
SaveChanges(). Tanpa pemanggilan itu, semua perubahan hanya berada di memori milik change tracker dan tidak pernah dikirim ke basis data, lalu hilang ketika context dibuang. Perbaikannya adalah memanggilSaveChangessetelah satu kelompok perubahan selesai. - Mengharapkan ID kembali ke 1 setelah baris lama dihapus. Kolom kunci utama yang bernilai berurutan tidak menggunakan ulang nomor yang pernah diberikan, sehingga demo pada topik ini menghapus berkas basis datanya, bukan sekadar isinya, agar setiap eksekusi dimulai dari keadaan yang sama.
- Memanggil
dotnet efsebelum alatnya dipasang. Perintah ditolak dengan pesanCould not execute because the specified command or file was not foundkarenadotnet efbukan bagian dari SDK, melainkan alat global terpisah. Perbaikannya adalah menjalankandotnet tool install --global dotnet-efsekali di awal.
Ringkasan#
- EF Core adalah ORM resmi .NET yang menerjemahkan operasi pada objek menjadi SQL melalui provider, dan provider SQLite membuat contoh berjalan tanpa server dengan hanya satu berkas basis data.
- Kelas entity menyatakan bentuk baris,
DbContextmenjadi sesi kerja, sedangkanDbSet<T>menjadi titik awal query sekaligus pintu masuk change tracker. - Empat operasi CRUD berujung pada
SaveChanges, yaituAddmenjadi INSERT,Finddan query LINQ menjadi SELECT, perubahan properti menjadi UPDATE, danRemovemenjadi DELETE. EnsureCreatedcocok untuk demo tanpa catatan skema, sedangkan migration mencatat setiap langkah perubahan skema agar basis data dapat dievolusi secara teratur.
Referensi#
- Overview of Entity Framework Core — Microsoft Learn — Ikhtisar EF Core, mulai dari model, query, hingga perbandingan provider yang tersedia.
- Getting Started — Microsoft Learn — Tutorial resmi aplikasi pertama dengan EF Core beserta langkah dasarnya.
- Migrations Overview — Microsoft Learn — Dokumentasi migration, mulai dari pembuatan, penerapan, hingga tabel riwayatnya.
- Change Tracking — Microsoft Learn — Penjelasan resmi tentang change tracker dan status entity yang dilacaknya.
- File-based apps — Microsoft Learn
— Dokumentasi aplikasi berkas tunggal .NET, termasuk default Native AOT dan arahan
#:property.