

Triển khai Repository Pattern và Unit of Work trong ASP.NET Core
- 01-07-2025
- Toanngo92
- 0 Comments
Mục lục
13.1 Giới thiệu
Để cung cấp khả năng kết nối cơ sở dữ liệu thông qua SQL Server hoặc Oracle, các tầng sau được sử dụng trong các ứng dụng ASP.NET MVC 5 cổ điển:
- Model
- View
- Controller
Trong đó:
- View được dùng để hiển thị giao diện người dùng.
- Model chịu trách nhiệm tương tác với cơ sở dữ liệu.
- Controller đảm nhận việc xử lý các thao tác CRUD.
Model sử dụng ADO.NET, một phần của ORM trong .NET framework, để tạo ra các đối tượng nghiệp vụ và thực hiện các hoạt động CRUD cơ bản. Tuy nhiên, trong ASP.NET Core, việc sử dụng repository pattern được khuyến khích.
Repository pattern là một mẫu thiết kế cho phép tạo một lớp trung gian giữa tầng truy cập dữ liệu và tầng nghiệp vụ, cho phép thao tác với dữ liệu mà không cần biết đến nguồn gốc cụ thể. Nó giúp quản lý logic truy cập dữ liệu một cách tập trung và độc lập với logic nghiệp vụ.
13.2 Mẫu Repository
Repository giúp gom logic truy cập dữ liệu vào một lớp riêng gọi là repository. Lớp này không phải là controller. Một repository là một lớp được định nghĩa cho từng entity và xử lý tất cả các hoạt động CRUD cho entity đó.
Ví dụ minh họa
Một repository cho entity Department
sẽ bao gồm các thao tác CRUD tiêu chuẩn và có thể thêm các phép xử lý tùy chỉnh khác. Như minh họa ở Hình 13.1, trong repository pattern, controller kết nối với DbContext
thông qua repository. DbContext
trực tiếp làm việc với cơ sở dữ liệu.
13.2.1 Non-Generic Repository Pattern (Mẫu Repository không tổng quát)
Tất cả các thao tác cơ sở dữ liệu liên quan đến một entity cụ thể được định nghĩa trong một repository không tổng quát, nghĩa là một lớp riêng biệt cho mỗi entity như Student
, Employee
, v.v.
Ví dụ, nếu có entity Movies
, một repository tên là NonGenericRepository
sẽ được tạo cho Movies
. Trong Code Snippet 1, repository xử lý thao tác Insert
, GetAll
và SaveChanges
.
public class NonGenericRepository {
public readonly MoviesDbContext dbContext;
private DbSet<Movies> entity;
public NonGenericRepository() {
dbContext = new MoviesDbContext();
entity = dbContext.Set<Movies>();
}
public void Insert(Movies entity) {
entity.Add(entity);
dbContext.SaveChanges();
}
public IEnumerable<Movies> GetAll() {
return dbContext.Movies.ToList();
}
public void SaveChanges() {
dbContext.SaveChanges();
}
}
13.2.2 Generic Repository Pattern (Mẫu Repository tổng quát)
Mẫu này được sử dụng để thực hiện các thao tác phổ biến như CRUD cho mọi entity bằng cách sử dụng một lớp tổng quát duy nhất.
Code Snippet 2 là ví dụ về một generic repository:
public class GenericRepository<T> : IGenericRepository<T> where T : BaseEntity {
private readonly ApplicationDbContext dbContext;
private DbSet<T> entities;
public GenericRepository(ApplicationDbContext dbcontext) {
dbContext = dbcontext;
this.entities = dbContext.Set<T>();
}
public void Insert(T entity) {
if (entity == null)
throw new ArgumentNullException("Entity Missing");
entities.Add(entity);
dbContext.SaveChanges();
}
public IEnumerable<T> GetAll() {
return entities.AsEnumerable();
}
public void SaveChanges() {
dbContext.SaveChanges();
}
}
Ghi chú:
T
được sử dụng để tham chiếu bất kỳ kiểu lớp nào.- Trong khi khởi tạo,
DbSet
được ánh xạ vớiT
. Insert
,GetAll
,SaveChanges
hoạt động cho bất kỳ entity nào.
Gọi repository kiểu tổng quát:
GenericRepository<Movies> objMovies = new GenericRepository<Movies>();
GenericRepository<Theatre> objTheatre = new GenericRepository<Theatre>();
Lợi ích của Repository Pattern
Repository giúp truy vấn và ánh xạ dữ liệu hiệu quả. Mô hình này phân tách logic truy cập dữ liệu ra khỏi logic nghiệp vụ, giúp dễ bảo trì và tái sử dụng.
Một số lợi ích:
Tính năng | Mô tả |
---|---|
Dễ kiểm thử (Easier Testing) | Phân tách rõ ràng các tầng giúp kiểm thử dễ dàng hơn |
Tách biệt trách nhiệm (Concerns are Separated) | Tách riêng logic dữ liệu và nghiệp vụ giúp dễ phát triển và bảo trì |
Giảm sự phụ thuộc (Loose Connection) | Khi thay đổi cơ sở dữ liệu, không cần sửa logic nghiệp vụ vì chúng đã tách biệt |
Dưới đây là bản dịch đầy đủ, chi tiết, không rút gọn từ các trang bạn vừa gửi (Session 13):
13.3 Mô hình Đơn vị Công việc (Unit of Work)
Khái niệm Đơn vị Công việc (Unit of Work – UoW) rất quan trọng để triển khai thành công mẫu repository. UoW là một dạng giao dịch kinh doanh kết hợp tất cả các thao tác CRUD như thêm/sửa/xóa vào một giao dịch duy nhất. Điều này đảm bảo rằng các giao dịch được hoàn thành cùng một lúc thay vì nhiều giao dịch riêng biệt.
Đối với mọi thay đổi, chỉ một lệnh commit được thực hiện. Bất kỳ giao dịch nào không đảm bảo tính toàn vẹn dữ liệu đều bị quay lại. UoW không khóa bất kỳ bảng dữ liệu nào cho đến khi tất cả thay đổi được xác nhận.
Hình 13.2 thể hiện sự khác biệt giữa mẫu repository và mẫu repository kết hợp với UoW:
Hình 13.2: Mẫu Repository và Repository kết hợp với UoW
Trong Hình 13.2, bộ điều khiển được đơn giản hóa vì chỉ chứa một repository kết nối với DbContext để truy cập cơ sở dữ liệu. Khi có nhiều repository, mỗi repository sẽ xử lý một thực thể tương ứng. Trong trường hợp này, mỗi repository sẽ có một phiên bản riêng của DbContext. Đôi khi, phương thức SaveChanges()
(của một repository) thất bại trong khi các phương thức khác thành công, điều này dẫn đến lỗi bất nhất dữ liệu.
Để giải quyết vấn đề này, một lớp DbContext duy nhất được sử dụng và chia sẻ giữa các repository. Lớp này được gọi là đơn vị công việc (Unit of Work). Lớp DbContext quản lý các thay đổi trong nhiều repository. Nó lưu tất cả các thay đổi thành một giao dịch duy nhất trong cơ sở dữ liệu. Điều này đảm bảo một giao dịch duy nhất chạy cho nhiều repository hoặc thất bại hoặc thành công toàn phần. UoW đảm bảo sự phân tách hoàn toàn giữa tầng dịch vụ và tầng dữ liệu.
Code Snippet 3
public interface IUnitOfWork : IDisposable
{
IMoviesRepository Movies { get; }
ITheatreRepository Theatres { get; }
int SaveChanges();
}
public class UnitOfWork : IUnitOfWork
{
private readonly DatabaseContext db;
public UnitOfWork()
{
db = new DatabaseContext();
}
private IMoviesRepository _movies;
public IMoviesRepository Movies
{
get
{
if (this._movies == null)
{
this._movies = new MoviesRepository(db);
}
return this._movies;
}
}
private ITheatreRepository _theatres;
public ITheatreRepository Theatres
{
get
{
if (this._theatres == null)
{
this._theatres = new TheatreRepository(db);
}
return this._theatres;
}
}
public int SaveChanges()
{
return db.SaveChanges();
}
public void Dispose()
{
db.Dispose();
}
}
Trong Code Snippet 3 trên, có hai repository: MoviesRepository
và TheatreRepository
. Một lớp có tên là UnitOfWork
được sử dụng để quản lý hai repository này.
13.3.1 Lợi ích của Đơn vị Công việc (Benefits of Unit of Work)
Việc triển khai mẫu repository kết hợp với UoW mang lại nhiều lợi ích. Một số lợi ích bao gồm:
- Đảm bảo sự linh hoạt trong kiến trúc vì tách biệt tầng nghiệp vụ và tầng dữ liệu.
- Đảm bảo dễ dàng kiểm thử thông qua triển khai kiểm thử đơn vị.
- Tăng mức độ trừu tượng do tách biệt logic nghiệp vụ và truy cập cơ sở dữ liệu.
- Không trùng lặp mã mặc dù có nhiều lớp.
- Dễ dàng quản lý các thao tác trong bộ nhớ cơ sở dữ liệu thông qua một giao dịch duy nhất.
Bài tập
