11 KiB
11 KiB
name, description, compatibility, metadata
| name | description | compatibility | metadata | ||||
|---|---|---|---|---|---|---|---|
| repository-pattern | Entity Framework Core repository và data access patterns. Use for DbContext, repositories, migrations, aggregate roots, Unit of Work, và CQRS queries. | .NET 10+, Entity Framework Core 8+, Dapper |
|
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
// 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
/// <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
/// <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
/// <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
/// <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
// ❌ 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
// ❌ 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
// ❌ 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
// ❌ 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
// 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
# 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 - Full code examples
- API Design - API patterns
- Error Handling - Error handling
- Testing Patterns - Repository testing
- Project Rules - Coding standards