Episode ini membahas akses data dengan Entity Framework Core: DbContext dan model, operasi CRUD, migrations dan relasi antar entitas, querying dengan LINQ dan raw SQL, serta provider database seperti SQL Server, PostgreSQL, dan SQLite.

Hampir semua aplikasi nyata menyimpan data secara permanen di database. Di ekosistem .NET, cara paling umum melakukannya adalah lewat Entity Framework Core — Object-Relational Mapper yang memetakan class C# menjadi tabel dan baris.
Dengan EF Core, kalian bekerja dengan database sebagai objek dan LINQ, bukan menulis SQL manual untuk setiap operasi. Meski begitu, memahami apa yang terjadi di balik layar tetap penting agar query tidak lambat di production.
Episode 9 ini membahas setup DbContext, operasi CRUD, migrations dan relationships, querying dengan LINQ dan raw SQL, serta pilihan provider database.
Mulai dengan menambah paket EF Core dan provider yang dipilih. Contoh berikut memakai SQLite agar mudah dicoba:
dotnet add package Microsoft.EntityFrameworkCore
dotnet add package Microsoft.EntityFrameworkCore.Sqlite
dotnet add package Microsoft.EntityFrameworkCore.DesignPerintah dotnet add package Microsoft.EntityFrameworkCore.Sqlite menambahkan provider SQLite. Untuk PostgreSQL atau SQL Server, ganti dengan Npgsql.EntityFrameworkCore.PostgreSQL atau Microsoft.EntityFrameworkCore.SqlServer.
DbContext adalah unit kerja utama — objek yang merepresentasikan sesi dengan database:
class TokoContext : DbContext
{
public DbSet<Produk> Produk => Set<Produk>();
protected override void OnConfiguring(DbContextOptionsBuilder options)
{
options.UseSqlite("Data Source=toko.db");
}
}
class Produk
{
public int Id { get; set; }
public string Nama { get; set; } = "";
public decimal Harga { get; set; }
}Property DbSet<Produk> menjadi pintu akses ke tabel Produk. Convention EF Core mengubah class Produk menjadi tabel dengan kolom Id, Nama, dan Harga secara otomatis.
CRUD di EF Core dilakukan lewat method Add, Find, dan properti Remove, lalu disimpan dengan SaveChangesAsync:
using var db = new TokoContext();
await db.Database.EnsureCreatedAsync();
var kopi = new Produk { Nama = "Kopi Gayo", Harga = 85_000m };
db.Produk.Add(kopi);
await db.SaveChangesAsync();
var hasil = await db.Produk.FindAsync(kopi.Id);
hasil!.Harga = 90_000m;
await db.SaveChangesAsync();
db.Produk.Remove(hasil);
await db.SaveChangesAsync();
Console.WriteLine("CRUD selesai.");Perhatikan bahwa perubahan hanya diterapkan ke database saat SaveChangesAsync dipanggil. FindAsync membaca berdasarkan primary key dengan cache, dan Remove menandai baris untuk dihapus.
Migrations merekam perubahan skema database sebagai kode versioned, sehingga tim bisa menyinkronkan database tanpa manual SQL:
dotnet ef migrations add BuatTabelProduk
dotnet ef database updatePerintah dotnet ef migrations add BuatTabelProduk menghasilkan file migration, dan dotnet ef database update menerapkannya ke database. File migration harus di-commit ke Git agar semua developer punya riwayat skema yang sama.
Relasi dideklarasikan lewat navigation properties. Contoh relasi satu-ke-banyak:
class Kategori
{
public int Id { get; set; }
public string Nama { get; set; } = "";
public List<Produk> Produk { get; set; } = new();
}
class Produk
{
public int Id { get; set; }
public string Nama { get; set; } = "";
public int KategoriId { get; set; }
public Kategori Kategori { get; set; } = null!;
}EF Core mengubah KategoriId menjadi foreign key. Load data terkait dengan Include:
var kategori = await db.Kategori
.Include(k => k.Produk)
.FirstAsync();
Console.WriteLine($"{kategori.Nama} punya {kategori.Produk.Count} produk");Include melakukan join untuk menarik produk sekaligus, menghindari N+1 query.
Sebagian besar query ditulis dengan LINQ yang di-translate ke SQL. Untuk kasus kompleks, FromSqlRaw mengeksekusi SQL mentah:
var murah = await db.Produk
.Where(p => p.Harga < 100_000m)
.OrderByDescending(p => p.Harga)
.ToListAsync();
var manual = await db.Produk
.FromSqlRaw("SELECT * FROM Produk WHERE Harga < 100")
.ToListAsync();
Console.WriteLine($"LINQ: {murah.Count}, raw SQL: {manual.Count}");Gunakan LINQ sebagai default; FromSqlRaw untuk query yang sulit diekspresikan. Selalu pakai parameterized SQL melalui FromSqlInterpolated — jangan pernah menyambungkan string input pengguna, karena rawan SQL injection (dibahas di episode 13).
EF Core bersifat provider-agnostik: sebagian besar kode tetap sama, hanya package dan baris Use yang berubah:
UseSqlServer.options.UseNpgsql(
"Host=localhost;Database=toko;Username=postgres;Password=rahasia");Ganti satu baris Use dan package, dan model serta migrations kalian sebagian besar tetap berfungsi. Inilah kekuatan abstraksi EF Core.
Inti yang harus dibawa pulang:
SaveChangesAsync dipanggil.Include mencegah N+1 query saat menarik relasi.Di episode 10 selanjutnya kita memastikan kualitas: testing dan quality assurance — unit testing dengan xUnit, mocking dengan Moq, integration testing dengan WebApplicationFactory, serta static analysis dan analyzer Roslyn.