Entity Framework Core: Optimasi Performa dan Praktik Terbaik di Tahun 2026
Kuasai optimasi performa EF Core 10 dengan AsNoTracking, compiled queries, split queries, batch operations, dan operator LeftJoin baru. Contoh C# praktis untuk aplikasi .NET 10 di produksi.

Entity Framework Core 10, yang dirilis bersama .NET 10 LTS pada November 2025, menghadirkan operator LeftJoin, pencarian vektor, named query filters, dan peningkatan signifikan pada translasi SQL. Panduan ini membahas praktik terbaik EF Core yang secara langsung memengaruhi kecepatan query, alokasi memori, dan skalabilitas di lingkungan produksi.
EF Core 10 merupakan rilis Long-Term Support, didukung hingga November 2028. Framework ini memerlukan SDK dan runtime .NET 10. Aplikasi yang masih menggunakan .NET 8 sebaiknya menargetkan EF Core 8 (LTS) hingga proses migrasi selesai.
Query Tracking: Kapan Harus Dinonaktifkan dan Mengapa
Setiap pemanggilan DbSet<T> secara default melampirkan entitas yang dikembalikan ke change tracker. Tracker ini memelihara snapshot dari nilai properti asli, menghitung perbedaan saat SaveChanges, dan menyelesaikan konflik identitas di seluruh navigasi. Overhead tersebut tidak diperlukan ketika data langsung mengalir ke respons API atau view model yang bersifat read-only.
public async Task<List<ProductDto>> GetActiveByCategoryAsync(
int categoryId, CancellationToken ct)
{
return await _context.Products
.AsNoTracking() // skip change tracker entirely
.Where(p => p.CategoryId == categoryId && p.IsActive)
.OrderBy(p => p.Name)
.Select(p => new ProductDto // project to DTO at the database level
{
Id = p.Id,
Name = p.Name,
Price = p.Price
})
.ToListAsync(ct);
}AsNoTracking menghilangkan overhead pembuatan snapshot dan resolusi identitas per entitas. Dikombinasikan dengan Select, hanya kolom yang dibutuhkan yang dikirim melalui jaringan. Pada tabel dengan 50.000 baris, pola ini biasanya mengurangi alokasi memori sebesar 40-60% dibandingkan query full-entity yang dilacak.
Untuk context yang tidak pernah memodifikasi data, atur default saat registrasi:
builder.Services.AddDbContext<CatalogContext>(options =>
options.UseSqlServer(connectionString)
.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking));Split Queries untuk Menghindari Ledakan Kartesian
Memuat entitas dengan beberapa navigasi koleksi melalui Include menghasilkan satu pernyataan SQL dengan JOIN. Ketika dua atau lebih koleksi dimuat secara bersamaan, result set berkembang sebagai produk Kartesian dari koleksi-koleksi tersebut, menduplikasi data baris induk di setiap kombinasi.
public async Task<Order?> GetWithDetailsAsync(int orderId, CancellationToken ct)
{
return await _context.Orders
.AsSplitQuery() // one SQL per Include
.Include(o => o.Items)
.ThenInclude(i => i.Product)
.Include(o => o.Payments)
.Include(o => o.ShippingEvents)
.FirstOrDefaultAsync(o => o.Id == orderId, ct);
}AsSplitQuery memecah pemuatan menjadi pernyataan SQL terpisah untuk setiap navigasi. Trade-off-nya: beberapa round-trip alih-alih satu, tetapi setiap result set tetap kecil dan menghindari masalah duplikasi baris. EF Core 10 juga memperbaiki inkonsistensi pengurutan yang sudah lama ada dalam split queries, memastikan pengurutan subquery sesuai dengan query utama.
Single query tetap lebih baik ketika memuat satu navigasi koleksi atau ketika latensi round-trip tinggi (pemanggilan database lintas-region). Lakukan benchmark kedua mode untuk pola akses spesifik sebelum mengambil keputusan.
Operasi Batch dengan ExecuteUpdate dan ExecuteDelete
Alur kerja EF tradisional memuat entitas, memodifikasi properti, lalu memanggil SaveChanges. Untuk operasi massal yang memengaruhi ribuan baris, pendekatan ini menghasilkan ribuan instance yang dilacak dan pernyataan UPDATE individual. EF Core 7 memperkenalkan ExecuteUpdateAsync dan ExecuteDeleteAsync untuk mendorong operasi menjadi satu perintah SQL. EF Core 10 menyederhanakan API lebih lanjut dengan menerima lambda biasa alih-alih expression tree, seperti yang dijelaskan dalam dokumentasi EF Core 10.
public async Task ApplySeasonalDiscountAsync(
int categoryId, decimal discountPercent, CancellationToken ct)
{
var affected = await _context.Products
.Where(p => p.CategoryId == categoryId && p.IsActive)
.ExecuteUpdateAsync(s =>
{
s.SetProperty(p => p.Price, p => p.Price * (1 - discountPercent / 100));
s.SetProperty(p => p.LastModified, DateTime.UtcNow);
}, ct);
// affected = number of rows updated
}Pernyataan ini diterjemahkan menjadi satu UPDATE ... SET ... WHERE. Tidak ada entitas yang dimuat ke memori. Untuk pembaruan 10.000 baris, waktu eksekusi turun dari hitungan detik (pendekatan tracked) menjadi milidetik.
Pola yang sama berlaku untuk penghapusan:
public async Task PurgeExpiredSessionsAsync(CancellationToken ct)
{
await _context.Sessions
.Where(s => s.ExpiresAt < DateTime.UtcNow)
.ExecuteDeleteAsync(ct);
}Siap menguasai wawancara .NET Anda?
Berlatih dengan simulator interaktif, flashcards, dan tes teknis kami.
Operator LeftJoin di EF Core 10
Sebelum EF Core 10, melakukan LEFT JOIN memerlukan kombinasi GroupJoin, SelectMany, dan DefaultIfEmpty dalam pola tertentu yang kebanyakan pengembang harus mencari referensinya setiap kali. EF Core 10 menambahkan LeftJoin sebagai operator LINQ kelas satu.
public async Task<List<EmployeeReportDto>> GetEmployeeDepartmentReportAsync(
CancellationToken ct)
{
return await _context.Employees
.LeftJoin(
_context.Departments,
employee => employee.DepartmentId,
department => department.Id,
(employee, department) => new EmployeeReportDto
{
FullName = employee.FirstName + " " + employee.LastName,
Department = department.Name ?? "Unassigned",
HiredAt = employee.HiredAt
})
.OrderBy(r => r.FullName)
.ToListAsync(ct);
}SQL yang dihasilkan menggunakan klausa LEFT JOIN standar. Operator RightJoin juga tersedia. Kedua operator ini menghilangkan rantai tiga metode yang verbose yang sebelumnya diperlukan.
Named Query Filters untuk Pemfilteran Multi-Concern
Global query filters telah dibatasi pada satu filter per tipe entitas sejak EF Core 2.0. Aplikasi yang mengimplementasikan soft-delete dan multi-tenancy harus menggabungkan kondisi menjadi satu ekspresi dan tidak dapat menonaktifkannya secara selektif. EF Core 10 memperkenalkan named query filters.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Invoice>()
.HasQueryFilter("SoftDelete", i => !i.IsDeleted)
.HasQueryFilter("Tenant", i => i.TenantId == _tenantId);
}Endpoint admin dapat menonaktifkan filter soft-delete sambil tetap menjaga isolasi tenant:
public async Task<List<Invoice>> GetAllIncludingDeletedAsync(CancellationToken ct)
{
return await _context.Invoices
.IgnoreQueryFilters(["SoftDelete"]) // tenant filter still applied
.ToListAsync(ct);
}Hal ini menghilangkan kebutuhan metode ekstensi IQueryable kustom yang secara manual menambahkan kondisi filter.
Ketahanan Koneksi dan Konfigurasi Pooling
Kegagalan database sementara (gangguan jaringan, failover Azure SQL, kehabisan connection pool) menyebabkan exception yang sering membuat pipeline request crash. EF Core menyediakan logika retry bawaan, tetapi konfigurasi default memerlukan pengaturan eksplisit.
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(connectionString, sqlOptions =>
{
sqlOptions.EnableRetryOnFailure(
maxRetryCount: 5,
maxRetryDelay: TimeSpan.FromSeconds(10),
errorNumbersToAdd: null); // retry on all transient errors
sqlOptions.CommandTimeout(30); // 30-second command timeout
}));Parameter pooling connection string juga berpengaruh:
Server=db.example.com;Database=AppDb;Min Pool Size=5;Max Pool Size=100;Connection Timeout=15;Min Pool Size menjaga koneksi yang siap pakai untuk lonjakan traffic. Max Pool Size membatasi total koneksi terbuka untuk mencegah overload database. Nilai default 100 cocok untuk sebagian besar aplikasi web, tetapi layanan dengan throughput tinggi mungkin perlu penyesuaian berdasarkan volume query konkuren aktual.
Retry otomatis tidak berfungsi di dalam transaksi yang diinisiasi pengguna. Bungkus seluruh transaksi dalam strategi retry manual menggunakan context.Database.CreateExecutionStrategy().ExecuteAsync(...) untuk menangani kegagalan sementara di beberapa operasi.
Strategi Indexing dan Analisis Query
Migrasi EF Core dapat mendefinisikan indeks secara deklaratif, tetapi memilih kolom mana yang akan diindeks memerlukan pemahaman pola query.
modelBuilder.Entity<Order>(entity =>
{
// composite index for frequent query pattern
entity.HasIndex(o => new { o.CustomerId, o.Status, o.CreatedAt })
.HasDatabaseName("IX_Order_Customer_Status_Date");
// filtered index for active orders only
entity.HasIndex(o => o.Status)
.HasFilter("[Status] <> 'Cancelled'")
.HasDatabaseName("IX_Order_ActiveStatus");
});Indeks terfilter mengurangi ukuran indeks dengan mengecualikan baris yang tidak pernah ditargetkan oleh query. Untuk tabel dengan 80% pesanan yang dibatalkan, indeks terfilter pada status aktif bisa 5 kali lebih kecil dan lebih cepat untuk dipindai.
Untuk menganalisis SQL yang dihasilkan, aktifkan logging di development:
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(connectionString)
.LogTo(Console.WriteLine, LogLevel.Information)
.EnableSensitiveDataLogging());Salin SQL yang tercatat ke SQL Server Management Studio dan jalankan dengan SET STATISTICS IO ON untuk memeriksa logical reads, atau gunakan EXPLAIN ANALYZE di PostgreSQL. Saran indeks yang hilang dari query plan sering mengungkapkan peluang optimasi dengan dampak tertinggi.
Compiled Queries untuk Hot Paths
Expression tree LINQ diurai dan diterjemahkan ke SQL pada setiap eksekusi. Untuk query yang berjalan ribuan kali per menit (misalnya pencarian autentikasi, validasi sesi), biaya translasi ini terakumulasi. Compiled queries meng-cache translasi saat aplikasi dimulai.
public static class UserQueries
{
// compiled once, reused on every call
public static readonly Func<AppDbContext, string, CancellationToken, Task<UserSession?>>
GetActiveSession = EF.CompileAsyncQuery(
(AppDbContext ctx, string token, CancellationToken ct) =>
ctx.UserSessions
.AsNoTracking()
.FirstOrDefault(s => s.Token == token && s.ExpiresAt > DateTime.UtcNow));
}
// Usage in middleware
var session = await UserQueries.GetActiveSession(dbContext, bearerToken, ct);Compiled queries melewati fase parsing expression tree sepenuhnya. Perbedaan performa hanya terukur pada jalur frekuensi tinggi (1.000+ panggilan/menit). Untuk endpoint CRUD standar, cache query bawaan di EF Core sudah menangani penggunaan ulang translasi.
Translasi Parameterized Collection di EF Core 10
Query yang memfilter berdasarkan daftar ID merupakan salah satu pola paling umum dalam akses data. EF Core 10 mengubah strategi translasi default untuk parameterized collections. Alih-alih meng-encode daftar sebagai array JSON (EF Core 8-9) atau menyisipkan konstanta secara inline (EF Core 7 dan sebelumnya), EF Core 10 menerjemahkan setiap nilai sebagai parameter SQL terpisah.
// Before (EF Core 8-9): JSON array parameter
// @__ids_0='[1,2,3]'
// SELECT ... WHERE Id IN (SELECT value FROM OPENJSON(@__ids_0))
// EF Core 10: individual parameters with padding
// SELECT ... WHERE Id IN (@ids1, @ids2, @ids3)
var orderIds = new[] { 101, 205, 389 };
var orders = await _context.Orders
.Where(o => orderIds.Contains(o.Id))
.ToListAsync(ct);Pendekatan baru ini memberikan informasi kardinalitas kepada query planner database sambil tetap menghindari bloat cache plan melalui parameter padding. Untuk kasus di mana pendekatan JSON berkinerja lebih baik (koleksi yang sangat besar), override perilaku per query:
var orders = await _context.Orders
.Where(o => EF.Parameter(orderIds).Contains(o.Id)) // force JSON mode
.ToListAsync(ct);Mulai berlatih!
Uji pengetahuan Anda dengan simulator wawancara dan tes teknis kami.
Sumber Referensi
- EF Core 10 What's New - dokumentasi resmi fitur-fitur baru
- Change Tracking in EF Core - penjelasan detail perilaku tracking
- EF Core Performance - panduan performa dari Microsoft
- GitHub: EF Core Release Notes - patch notes dan breaking changes
Checklist Performa EF Core untuk Produksi
- Gunakan
AsNoTracking()dan proyeksiSelectpada setiap query read-only untuk menghilangkan overhead change tracker - Terapkan
AsSplitQuery()saat memuat beberapa navigasi koleksi untuk menghindari ledakan Kartesian - Ganti pola tracked load-modify-save dengan
ExecuteUpdateAsyncdanExecuteDeleteAsyncuntuk operasi massal - Adopsi operator
LeftJoindari EF Core 10 untuk menggantikan rantaiGroupJoin/SelectMany/DefaultIfEmptyyang verbose - Konfigurasi named query filters untuk mengelola soft-delete dan multi-tenancy secara independen
- Atur
EnableRetryOnFailuredan sesuaikan ukuran connection pool untuk ketahanan produksi - Definisikan indeks komposit dan terfilter berdasarkan pola query aktual, bukan dugaan
- Cadangkan compiled queries untuk jalur yang benar-benar panas yang melebihi 1.000 eksekusi per menit
- Biarkan EF Core 10 menangani translasi parameterized collection secara default, dan override hanya ketika benchmark membenarkannya
Untuk persiapan wawancara, modul EF Core Advanced membahas pola-pola ini secara mendalam. Panduan C# LINQ menjelaskan operator query yang mendasarinya, dan panduan Clean Architecture dengan .NET mendemonstrasikan cara menyusun lapisan repository dan service yang membungkus query-query ini.
Mulai berlatih!
Uji pengetahuan Anda dengan simulator wawancara dan tes teknis kami.
Bisakah kamu menemukan bug di .NET?
Satu potongan kode nyata, satu bug tersembunyi, satu percobaan per hari. Tanpa akun untuk mencoba.

Ditulis oleh
Anthony Fillion-MailletPendiri SharpSkill
Developer fullstack selama lebih dari 10 tahun. Ia menjalankan SharpSkill dan bertanggung jawab atas semua yang diterbitkan di sini.
Diperbarui 7 September 2026
Tag
Bagikan
Artikel terkait

Manajemen Lifetime DbContext di ASP.NET Core: Performa vs Keamanan Thread pada Operasi Async
Pelajari cara mengelola lifetime DbContext di ASP.NET Core secara optimal. Pahami kapan menggunakan scoped vs transient, cara menangani operasi async dengan aman, dan optimasi performa dengan DbContext pooling.

Clean Code Architecture C#: Panduan Lengkap dan Pertanyaan Interview 2026
Pelajari Clean Code Architecture di C# dengan prinsip SOLID, pola Repository, Dependency Injection, dan pertanyaan interview yang sering muncul untuk developer .NET.

Clean Architecture .NET 2026: CQRS, MediatR, dan Pertanyaan Interview Developer
Panduan lengkap Clean Architecture di .NET dengan CQRS dan MediatR 14. Pelajari pola arsitektur berlapis, pipeline behaviors, dan persiapan interview developer senior.