Entity Framework Core
Panduan menggunakan Entity Framework Core untuk akses database
Entity Framework Core
Entity Framework Core (EF Core) adalah ORM (Object-Relational Mapper) modern dari Microsoft. Dengan EF Core, Anda bisa bekerja dengan database menggunakan objek C# tanpa menulis SQL secara langsung.
Apa itu ORM?
ORM adalah layer yang menghubungkan kode objek (C#) dengan database relasional:
C# Objects <--> EF Core <--> Database
Keuntungan:
- Tidak perlu menulis SQL manual
- Type-safe queries dengan LINQ
- Change tracking otomatis
- Migration untuk version control schema
Instalasi
# Package utama
dotnet add package Microsoft.EntityFrameworkCore
# Provider database (pilih salah satu)
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.PostgreSQL
dotnet add package Microsoft.EntityFrameworkCore.Sqlite
# Tools untuk migration
dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet tool install --global dotnet-ef
Setup Dasar
1. Buat Entity (Model)
public class Product
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public decimal Price { get; set; }
public int CategoryId { get; set; }
// Navigation property
public Category Category { get; set; } = null!;
}
public class Category
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
// Navigation property (one-to-many)
public ICollection<Product> Products { get; set; } = new List<Product>();
}
2. Buat DbContext
using Microsoft.EntityFrameworkCore;
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options)
: base(options)
{
}
public DbSet<Product> Products { get; set; }
public DbSet<Category> Categories { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Konfigurasi relasi
modelBuilder.Entity<Product>()
.HasOne(p => p.Category)
.WithMany(c => c.Products)
.HasForeignKey(p => p.CategoryId);
// Seed data
modelBuilder.Entity<Category>().HasData(
new Category { Id = 1, Name = "Electronics" },
new Category { Id = 2, Name = "Books" }
);
}
}
3. Register di Program.cs
var builder = WebApplication.CreateBuilder(args);
// SQL Server
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
// Atau SQLite (untuk development)
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlite("Data Source=app.db"));
Migrations
Migrations adalah cara EF Core mengelola perubahan schema database:
# Buat migration pertama
dotnet ef migrations add InitialCreate
# Lihat SQL yang akan dijalankan
dotnet ef migrations script
# Terapkan migration ke database
dotnet ef database update
# Rollback migration
dotnet ef database update PreviousMigrationName
# Hapus migration terakhir (belum diterapkan)
dotnet ef migrations remove
CRUD Operations
Create
public class ProductService
{
private readonly AppDbContext _context;
public ProductService(AppDbContext context)
{
_context = context;
}
public async Task<Product> CreateAsync(string name, decimal price, int categoryId)
{
var product = new Product
{
Name = name,
Price = price,
CategoryId = categoryId
};
_context.Products.Add(product);
await _context.SaveChangesAsync();
return product;
}
}
Read
// Get all
public async Task<List<Product>> GetAllAsync()
{
return await _context.Products.ToListAsync();
}
// Get by ID
public async Task<Product?> GetByIdAsync(int id)
{
return await _context.Products.FindAsync(id);
}
// Include navigation property
public async Task<Product?> GetWithCategoryAsync(int id)
{
return await _context.Products
.Include(p => p.Category)
.FirstOrDefaultAsync(p => p.Id == id);
}
Update
public async Task UpdateAsync(int id, string name, decimal price)
{
var product = await _context.Products.FindAsync(id);
if (product == null) return;
product.Name = name;
product.Price = price;
await _context.SaveChangesAsync();
}
Delete
public async Task DeleteAsync(int id)
{
var product = await _context.Products.FindAsync(id);
if (product == null) return;
_context.Products.Remove(product);
await _context.SaveChangesAsync();
}
LINQ Queries
Filtering
// Where
var expensiveProducts = await _context.Products
.Where(p => p.Price > 100000)
.ToListAsync();
// Multiple conditions
var filtered = await _context.Products
.Where(p => p.Price > 50000 && p.CategoryId == 1)
.ToListAsync();
Sorting
// Order by
var sorted = await _context.Products
.OrderBy(p => p.Name)
.ToListAsync();
// Descending
var sortedDesc = await _context.Products
.OrderByDescending(p => p.Price)
.ToListAsync();
// Multiple sort
var multiSort = await _context.Products
.OrderBy(p => p.CategoryId)
.ThenBy(p => p.Name)
.ToListAsync();
Pagination
public async Task<List<Product>> GetPagedAsync(int page, int pageSize)
{
return await _context.Products
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
}
Projection
// Select specific fields
var names = await _context.Products
.Select(p => p.Name)
.ToListAsync();
// Select ke DTO
var dtos = await _context.Products
.Select(p => new ProductDto
{
Id = p.Id,
Name = p.Name,
CategoryName = p.Category.Name
})
.ToListAsync();
Aggregation
int count = await _context.Products.CountAsync();
decimal total = await _context.Products.SumAsync(p => p.Price);
decimal avg = await _context.Products.AverageAsync(p => p.Price);
decimal? max = await _context.Products.MaxAsync(p => (decimal?)p.Price);
Relasi
One-to-Many
// Category has many Products
public class Category
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Product> Products { get; set; }
}
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public int CategoryId { get; set; }
public Category Category { get; set; }
}
Many-to-Many
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Tag> Tags { get; set; }
}
public class Tag
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Product> Products { get; set; }
}
// Di OnModelCreating
modelBuilder.Entity<Product>()
.HasMany(p => p.Tags)
.WithMany(t => t.Products);
Performance Tips
1. No-Tracking Queries
// Untuk read-only queries
var products = await _context.Products
.AsNoTracking()
.ToListAsync();
2. Explicit Loading
var product = await _context.Products.FindAsync(id);
await _context.Entry(product)
.Reference(p => p.Category)
.LoadAsync();
3. Split Queries
// Untuk queries dengan multiple includes
var categories = await _context.Categories
.Include(c => c.Products)
.AsSplitQuery()
.ToListAsync();
4. Raw SQL
// Ketika butuh performa maksimal
var products = await _context.Products
.FromSqlRaw("SELECT * FROM Products WHERE Price > {0}", 100000)
.ToListAsync();
Repository Pattern (Optional)
public interface IRepository<T> where T : class
{
Task<T?> GetByIdAsync(int id);
Task<List<T>> GetAllAsync();
Task AddAsync(T entity);
Task UpdateAsync(T entity);
Task DeleteAsync(T entity);
}
public class Repository<T> : IRepository<T> where T : class
{
protected readonly AppDbContext _context;
protected readonly DbSet<T> _dbSet;
public Repository(AppDbContext context)
{
_context = context;
_dbSet = context.Set<T>();
}
public async Task<T?> GetByIdAsync(int id) => await _dbSet.FindAsync(id);
public async Task<List<T>> GetAllAsync() => await _dbSet.ToListAsync();
public async Task AddAsync(T entity) => await _dbSet.AddAsync(entity);
public async Task UpdateAsync(T entity) => _dbSet.Update(entity);
public async Task DeleteAsync(T entity) => _dbSet.Remove(entity);
}
Langkah Selanjutnya
- Advanced EF Core — Complex queries, interceptors
- Unit of Work Pattern — Transaction management
- Database Performance — Indexing, optimization
EF Core sangat powerful tapi juga bisa jadi sumber masalah performa jika tidak digunakan dengan benar. Selalu monitor SQL yang dihasilkan!