---
name: repository-pattern
description: Entity Framework Core repository và data access patterns. Use for DbContext, repositories, migrations, aggregate roots, Unit of Work, và CQRS queries.
compatibility: ".NET 10+, Entity Framework Core 8+, Dapper"
metadata:
  author: Velik Ho
  version: "1.0"
---

# Repository Pattern / Mẫu Repository

Repository pattern cho GoodGo microservices theo chuẩn Domain-Driven Design (DDD).

## When to Use This Skill / Khi Nào Sử Dụng

Use this skill when:
- Creating new repositories for aggregates / Tạo repositories mới cho aggregates
- Implementing data access with EF Core / Triển khai data access với EF Core
- Setting up Unit of Work pattern / Cài đặt Unit of Work pattern
- Separating read/write operations (CQRS) / Phân tách read/write (CQRS)
- Creating database migrations / Tạo database migrations
- Optimizing query performance / Tối ưu hiệu năng query

## Core Concepts / Khái Niệm Cốt Lõi

### 1. One Repository Per Aggregate Root / Một Repository Cho Mỗi Aggregate Root

```
❌ WRONG: Repository cho từng table
   OrderRepository
   OrderItemRepository
   OrderPaymentRepository

✅ CORRECT: Repository chỉ cho Aggregate Root
   OrderRepository (quản lý Order + OrderItems + OrderPayments)
```

**Nguyên tắc:**
- Aggregate Root chịu trách nhiệm duy trì tính nhất quán
- Mọi thao tác với entities con phải thông qua Root
- Repository chỉ expose Aggregate Root, không expose entities con

### 2. Separated Interface / Tách Giao Diện

```
Domain Layer (ServiceName.Domain/)
├── AggregatesModel/
│   └── OrderAggregate/
│       ├── Order.cs           # Aggregate Root
│       ├── OrderItem.cs       # Entity con
│       └── IOrderRepository.cs # Interface

Infrastructure Layer (ServiceName.Infrastructure/)
├── Repositories/
│   └── OrderRepository.cs     # Implementation
└── Data/
    └── ApplicationDbContext.cs
```

**Lợi ích:**
- Domain không phụ thuộc vào công nghệ (EF Core, Dapper)
- Dễ thay thế implementation
- Tuân thủ Dependency Inversion Principle

### 3. Unit of Work / Đơn Vị Công Việc

```csharp
// EN: DbContext IS the Unit of Work in EF Core
// VI: DbContext CHÍNH LÀ Unit of Work trong EF Core
public interface IUnitOfWork : IDisposable
{
    Task<int> SaveChangesAsync(CancellationToken ct = default);
}

// EN: All repositories share the same DbContext
// VI: Tất cả repositories dùng chung một DbContext
```

### 4. CQRS Simplified / CQRS Đơn Giản

| Operation | Pattern | Tool |
|-----------|---------|------|
| **Commands** (Create, Update, Delete) | Repository + EF Core | Full domain model |
| **Queries** (Read) | Dapper / Raw SQL | Lightweight DTOs |

## Key Patterns / Mẫu Chính

### Repository Interface / Giao Diện Repository

```csharp
/// <summary>
/// EN: Base repository interface for aggregate roots.
/// VI: Interface repository cơ bản cho aggregate roots.
/// </summary>
public interface IRepository<T> where T : class, IAggregateRoot
{
    IUnitOfWork UnitOfWork { get; }
    
    Task<T?> GetByIdAsync(Guid id, CancellationToken ct = default);
    Task<T> AddAsync(T entity, CancellationToken ct = default);
    void Update(T entity);
    void Delete(T entity);
}

/// <summary>
/// EN: Order repository interface.
/// VI: Interface repository cho Order.
/// </summary>
public interface IOrderRepository : IRepository<Order>
{
    Task<Order?> GetWithItemsAsync(Guid id, CancellationToken ct = default);
    Task<IReadOnlyList<Order>> GetByUserIdAsync(string userId, CancellationToken ct = default);
}
```

### EF Core Implementation / Triển Khai EF Core

```csharp
/// <summary>
/// EN: EF Core repository implementation.
/// VI: Triển khai repository với EF Core.
/// </summary>
public class OrderRepository : IOrderRepository
{
    private readonly ApplicationDbContext _context;

    public IUnitOfWork UnitOfWork => _context;

    public OrderRepository(ApplicationDbContext context)
    {
        _context = context ?? throw new ArgumentNullException(nameof(context));
    }

    public async Task<Order?> GetByIdAsync(Guid id, CancellationToken ct = default)
    {
        return await _context.Orders.FindAsync(new object[] { id }, ct);
    }

    public async Task<Order?> GetWithItemsAsync(Guid id, CancellationToken ct = default)
    {
        return await _context.Orders
            .Include(o => o.OrderItems)
            .FirstOrDefaultAsync(o => o.Id == id, ct);
    }

    public async Task<Order> AddAsync(Order order, CancellationToken ct = default)
    {
        var entry = await _context.Orders.AddAsync(order, ct);
        return entry.Entity;
    }

    public void Update(Order order)
    {
        _context.Entry(order).State = EntityState.Modified;
    }

    public void Delete(Order order)
    {
        _context.Orders.Remove(order);
    }
}
```

### Command Handler with Repository / Handler với Repository

```csharp
/// <summary>
/// EN: Command handler using repository pattern.
/// VI: Command handler sử dụng repository pattern.
/// </summary>
public class CreateOrderCommandHandler : IRequestHandler<CreateOrderCommand, OrderResult>
{
    private readonly IOrderRepository _orderRepository;

    public CreateOrderCommandHandler(IOrderRepository orderRepository)
    {
        _orderRepository = orderRepository;
    }

    public async Task<OrderResult> Handle(
        CreateOrderCommand request,
        CancellationToken ct)
    {
        // EN: Create aggregate through domain model
        // VI: Tạo aggregate thông qua domain model
        var order = new Order(request.UserId, request.ShippingAddress);
        
        foreach (var item in request.Items)
        {
            order.AddItem(item.ProductId, item.Quantity, item.UnitPrice);
        }

        // EN: Persist through repository
        // VI: Lưu qua repository
        await _orderRepository.AddAsync(order, ct);
        await _orderRepository.UnitOfWork.SaveChangesAsync(ct);

        return new OrderResult(order.Id);
    }
}
```

### Query with Dapper (CQRS) / Query với Dapper

```csharp
/// <summary>
/// EN: Query handler using Dapper for read operations.
/// VI: Query handler dùng Dapper cho operations đọc.
/// </summary>
public class GetOrdersQueryHandler : IRequestHandler<GetOrdersQuery, IReadOnlyList<OrderDto>>
{
    private readonly IDbConnection _connection;

    public GetOrdersQueryHandler(IDbConnection connection)
    {
        _connection = connection;
    }

    public async Task<IReadOnlyList<OrderDto>> Handle(
        GetOrdersQuery request,
        CancellationToken ct)
    {
        // EN: Use Dapper for optimized read queries
        // VI: Dùng Dapper cho query đọc tối ưu
        const string sql = @"
            SELECT o.Id, o.Status, o.TotalAmount, o.CreatedAt,
                   COUNT(oi.Id) as ItemCount
            FROM Orders o
            LEFT JOIN OrderItems oi ON o.Id = oi.OrderId
            WHERE o.UserId = @UserId
            GROUP BY o.Id, o.Status, o.TotalAmount, o.CreatedAt
            ORDER BY o.CreatedAt DESC
            OFFSET @Skip ROWS FETCH NEXT @Take ROWS ONLY";

        var orders = await _connection.QueryAsync<OrderDto>(sql, new
        {
            request.UserId,
            request.Skip,
            request.Take
        });

        return orders.ToList();
    }
}
```

## Common Mistakes / Lỗi Thường Gặp

### 1. Repository Per Table / Repository Cho Từng Table

```csharp
// ❌ BAD: Multiple repositories for one aggregate
public class OrderItemRepository { }
public class OrderPaymentRepository { }

// ✅ GOOD: Only repository for aggregate root
public class OrderRepository
{
    // EN: Access child entities through aggregate
    // VI: Truy cập entities con thông qua aggregate
    public async Task<Order?> GetWithItemsAsync(Guid id, CancellationToken ct)
    {
        return await _context.Orders
            .Include(o => o.OrderItems)
            .Include(o => o.Payment)
            .FirstOrDefaultAsync(o => o.Id == id, ct);
    }
}
```

### 2. Bypassing Aggregate Root / Bỏ Qua Aggregate Root

```csharp
// ❌ BAD: Modifying child entity directly
var orderItem = await _context.OrderItems.FindAsync(itemId);
orderItem.Quantity = 5;

// ✅ GOOD: Modify through aggregate root
var order = await _orderRepository.GetWithItemsAsync(orderId, ct);
order.UpdateItemQuantity(itemId, 5);
await _orderRepository.UnitOfWork.SaveChangesAsync(ct);
```

### 3. Exposing IQueryable / Lộ IQueryable

```csharp
// ❌ BAD: Leaking implementation details
public interface IOrderRepository
{
    IQueryable<Order> GetAll();
}

// ✅ GOOD: Encapsulated queries
public interface IOrderRepository
{
    Task<IReadOnlyList<Order>> GetByUserIdAsync(string userId, CancellationToken ct);
    Task<Order?> GetWithItemsAsync(Guid id, CancellationToken ct);
}
```

### 4. Missing Unit of Work / Thiếu Unit of Work

```csharp
// ❌ BAD: SaveChanges in repository
public async Task<Order> AddAsync(Order order, CancellationToken ct)
{
    await _context.Orders.AddAsync(order, ct);
    await _context.SaveChangesAsync(ct);  // WRONG!
    return order;
}

// ✅ GOOD: SaveChanges in handler (Unit of Work)
public async Task<OrderResult> Handle(CreateOrderCommand request, CancellationToken ct)
{
    await _orderRepository.AddAsync(order, ct);
    await _orderRepository.UnitOfWork.SaveChangesAsync(ct);  // CORRECT!
}
```

## Quick Reference / Tham Chiếu Nhanh

### Project Structure

| Component | Location |
|-----------|----------|
| **Repository Interface** | `Domain/AggregatesModel/{Aggregate}/I{Aggregate}Repository.cs` |
| **Repository Implementation** | `Infrastructure/Repositories/{Aggregate}Repository.cs` |
| **DbContext** | `Infrastructure/Data/ApplicationDbContext.cs` |
| **Entity Configurations** | `Infrastructure/Data/Configurations/` |

### DI Registration

```csharp
// EN: Register repositories in Program.cs
// VI: Đăng ký repositories trong Program.cs
builder.Services.AddScoped<IOrderRepository, OrderRepository>();
builder.Services.AddScoped<IUserRepository, UserRepository>();

// EN: Register DbContext
// VI: Đăng ký DbContext
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseNpgsql(connectionString));

// EN: Register Dapper connection for queries
// VI: Đăng ký Dapper connection cho queries
builder.Services.AddScoped<IDbConnection>(_ =>
    new NpgsqlConnection(connectionString));
```

### Migration Commands

```bash
# EN: Add migration / VI: Thêm migration
dotnet ef migrations add MigrationName --project src/Service.Infrastructure

# EN: Update database / VI: Cập nhật database
dotnet ef database update --project src/Service.Infrastructure

# EN: Generate SQL script / VI: Tạo SQL script
dotnet ef migrations script --project src/Service.Infrastructure
```

## Resources / Tài Nguyên

- [Detailed Examples](./references/REFERENCE.md) - Full code examples
- [API Design](../api-design/SKILL.md) - API patterns
- [Error Handling](../error-handling-patterns/SKILL.md) - Error handling
- [Testing Patterns](../testing-patterns/SKILL.md) - Repository testing
- [Project Rules](../project-rules/SKILL.md) - Coding standards
