Repository Pattern và Unit of Work trong .NET Core

Repository Pattern và Unit of Work trong .NET Core

April 1, 2025 0 By Nam Vu

Xây dựng các ứng dụng mạnh mẽ và dễ bảo trì trong .NET Core có thể trở nên mượt mà như một điệu nhảy được biên đạo tốt—nếu bạn sử dụng đúng design pattern. Hai trong số những pattern phổ biến nhất trong quản lý dữ liệu là RepositoryUnit of Work, thường hoạt động như một cặp đôi hoàn hảo giúp đơn giản hóa việc truy xuất dữ liệu và đảm bảo mã nguồn sạch sẽ. Repository giúp trừu tượng hóa logic truy xuất dữ liệu cho từng thực thể, cung cấp một cấu trúc dễ kiểm thử và bảo trì, trong khi Unit of Work đảm bảo tính nhất quán giao dịch, xử lý tất cả các thao tác liên quan đến database như một đơn vị duy nhất.

Hãy tưởng tượng một thư viện. Người thủ thư (Repository) giúp bạn tìm sách mà không cần phải lục tung các kệ. Trong khi đó, quản lý thư viện (Unit of Work) đảm bảo mọi sách được mượn hoặc trả đều được ghi nhận chính xác vào cuối ngày. Trong lập trình, Repository quản lý logic truy vấn cho từng thực thể, còn Unit of Work điều phối các thao tác với database để duy trì tính toàn vẹn dữ liệu.

Repository Pattern là gì?

Repository đóng vai trò trung gian giữa ứng dụng và database. Thay vì tương tác trực tiếp với DbContext, ứng dụng sử dụng Repository để truy xuất, thêm, cập nhật hoặc xóa dữ liệu. Điều này giúp mã nguồn trở nên gọn gàng và có tính module cao hơn.

Lợi ích của Repository Pattern:

  • Trừu tượng hóa logic truy vấn: Tránh lặp lại các thao tác CRUD ở nhiều nơi trong ứng dụng.
  • Tập trung các truy vấn vào một nơi: Dễ bảo trì và mở rộng.
  • Cải thiện khả năng kiểm thử: Có thể mock repository trong unit test.

Ví dụ về Repository Pattern cho thực thể Book:

public interface IBookRepository
{
    Task<IEnumerable<Book>> GetAllAsync();
    Task<Book> GetByIdAsync(int id);
    Task AddAsync(Book book);
    Task UpdateAsync(Book book);
    Task DeleteAsync(int id);
}
public class BookRepository : IBookRepository
{
    private readonly LibraryContext _context;

    public BookRepository(LibraryContext context)
    {
        _context = context;
    }

    public async Task<IEnumerable<Book>> GetAllAsync() => await _context.Books.ToListAsync();

    public async Task<Book> GetByIdAsync(int id) => await _context.Books.FindAsync(id);

    public async Task AddAsync(Book book) => await _context.Books.AddAsync(book);

    public async Task UpdateAsync(Book book) => _context.Books.Update(book);

    public async Task DeleteAsync(int id)
    {
        var book = await _context.Books.FindAsync(id);
        if (book != null) _context.Books.Remove(book);
    }
}

Unit of Work là gì?

Unit of Work đảm bảo nhiều thao tác trên database được xử lý như một giao dịch duy nhất. Điều này rất quan trọng để duy trì tính toàn vẹn dữ liệu. Ví dụ, khi xử lý một transaction mượn sách, chúng ta cần:

  1. Đánh dấu sách là “đã mượn”.
  2. Ghi lại lịch sử mượn sách.

Nếu một trong hai thao tác này thất bại, dữ liệu sẽ không còn chính xác. Unit of Work đảm bảo rằng hoặc cả hai thao tác đều thành công, hoặc không có thao tác nào được thực hiện.

Lợi ích của Unit of Work:

  • Quản lý transaction để đảm bảo tính toàn vẹn dữ liệu.
  • Giảm số lần truy vấn database bằng cách gom các thao tác thành một batch.
  • Tập trung hóa việc commit dữ liệu, giúp dễ bảo trì và debug.

Ví dụ về Unit of Work Pattern:

public interface IUnitOfWork : IDisposable
{
    IBookRepository Books { get; }
    Task<int> CompleteAsync();
}
public class UnitOfWork : IUnitOfWork
{
    private readonly LibraryContext _context;

    public UnitOfWork(LibraryContext context)
    {
        _context = context;
        Books = new BookRepository(context);
    }

    public IBookRepository Books { get; private set; }

    public async Task<int> CompleteAsync()
    {
        return await _context.SaveChangesAsync();
    }

    public void Dispose()
    {
        _context.Dispose();
    }
}

Sử dụng Repository và Unit of Work trong Controller

Controller sử dụng Unit of Work để gọi Repository:

[ApiController]
[Route("api/[controller]")]
public class BooksController : ControllerBase
{
    private readonly IUnitOfWork _unitOfWork;

    public BooksController(IUnitOfWork unitOfWork)
    {
        _unitOfWork = unitOfWork;
    }

    [HttpGet]
    public async Task<IActionResult> GetAll()
    {
        var books = await _unitOfWork.Books.GetAllAsync();
        return Ok(books);
    }

    [HttpPost]
    public async Task<IActionResult> Add(Book book)
    {
        await _unitOfWork.Books.AddAsync(book);
        await _unitOfWork.CompleteAsync();
        return CreatedAtAction(nameof(GetAll), new { id = book.Id }, book);
    }
}

Vì sao nên dùng cả Repository và Unit of Work?

Sử dụng RepositoryUnit of Work kết hợp giúp ứng dụng đạt được:

  1. Separation of Concerns: Repository xử lý truy vấn dữ liệu, còn Unit of Work xử lý giao dịch.
  2. Tái sử dụng: Tách rời logic truy vấn giúp dễ dàng tái sử dụng trong nhiều phần của ứng dụng.
  3. Tính nhất quán: Unit of Work đảm bảo các thao tác liên quan thực thi đồng bộ, tránh lỗi cập nhật từng phần.
  4. Dễ mở rộng: Giúp mã nguồn dễ bảo trì, mở rộng và kiểm thử.

Repository PatternUnit of Work Pattern giúp việc truy cập dữ liệu trong .NET Core trở nên sạch hơn và hiệu quả hơn. Bằng cách phân tách rõ ràng giữa truy vấn dữ liệu và quản lý giao dịch, bạn có thể giữ cho mã nguồn gọn gàng, dễ bảo trì và có thể mở rộng một cách linh hoạt. Khi thiết kế tầng dữ liệu, hãy nhớ rằng những pattern này chính là “sự hoàn hảo” giúp bạn xây dựng hệ thống hiệu quả!

#ntechdevelopers