Docs .NET Development Entity Framework Core

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

  1. Advanced EF Core — Complex queries, interceptors
  2. Unit of Work Pattern — Transaction management
  3. 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!