Trong bài viết này, mình sẽ hướng dẫn các bạn sử dụng Repository Pattern với Unit Of Work trong ASP.NET Core.
Repository Pattern là gì?
Martin Fowler định nghĩa Repository Pattern như sau:
A Repository mediates between the domain and data mapping layers, acting like an in-memory domain object collection. Client objects construct query specifications declaratively and submit them to Repository for satisfaction. Objects can be added to and removed from the Repository, as they can from a simple collection of objects, and the mapping code encapsulated by the Repository will carry out the appropriate operations behind the scenes. Conceptually, a Repository encapsulates the set of objects persisted in a data store and the operations performed over them, providing a more object-oriented view of the persistence layer. Repository also supports the objective of achieving a clean separation and one-way dependency between the domain and data mapping layers.Trong mô hình 3 lớp (Presentation, Business Logic Layer, Data Access Layer) thì Repository là lớp trung gian nằm ở giữa Business Logic Layer và Data Access Layer. Implement pattern này đảm bảo những thay đổi sẽ được thông qua Repository, tránh những thay đổi rải rác trong ứng dụng.
Để đơn giản hóa ví dụ, mình sẽ lược bớt Business Logic Layer.
Unit Of Work
Unit Of Work được sử dụng để đảm bảo nhiều hành động như insert, update, delete...được thực thi trong cùng một transaction thống nhất. Nói đơn giản hơn, nghĩa là khi một hành động của người dùng tác động vào hệ thống, tất cả các hành động như insert, update, delete...phải thực hiện xong thì mới gọi là một transaction thành công. Gói tất cả các hành động đơn lẻ vào một transaction để đảm bảo tính toàn vẹn dữ liệu.
Ví dụ
Tạo ứng dụng ASP.NET Core
Trong Visual Studio 2022, bấm File, chọn New > Project.
Gõ Web API trong search box.
Chọn ASP.NET Core Web API template -> Next. Gõ tên project RepositoryPatternExample.
Chọn Framework (.NET 6.0 hoặc 7.0), check vào Use controller. Bấm Create.
Khai báo Model
Cài đặt package SQL Server:
dotnet add package Microsoft.EntityFrameworkCore
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
Tạo model entity trong folder Domain\Models:
public interface IEntity
{
int Id { get; set; }
}
public class Movie : IEntity
{
public int Id { get; set; }
public string Title { get; set; }
public string Genre { get; set; }
public DateTime ReleaseDate { get; set; }
}
public class Actor : IEntity
{
public int Id { get; set; }
public string Name { get;set; }
public string Gender { get;set; }
public int Followers { get; set; }
}
public class MyMovieContext: DbContext
{
public MyMovieContext(DbContextOptions<MyMovieContext> options): base(options)
{ }
public DbSet<Movie> Movies { get; set; }
public DbSet<Actor> Actors { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Movie>().Property(t => t.Title).HasMaxLength(60).IsRequired();
modelBuilder.Entity<Movie>().Property(t => t.Genre).HasMaxLength(30).IsRequired();
modelBuilder.Entity<Actor>().Property(t => t.Name).HasMaxLength(200).IsRequired();
}
}
Setup Migration
Migration là kỹ thuật trong việc tương tác với cơ sở dữ liệu, theo đó việc thay đổi về cấu trúc CSDL ở code sẽ được cập nhật lên CSDL đảm bảo dữ liệu đang tồn tại không bị mất, lịch sử (phiên bản) cập nhật được lưu lại sau mỗi lần cập nhật. Trong Terminal, bạn cài đặt EF Core Tool (trường hợp máy bạn chưa cài đặt)dotnet tool install --global dotnet-ef
Nâng cấp EF Core Tool lên version mới
dotnet tool update --global dotnet-ef
Để sử dụng Migration, bạn cần bổ sung thêm package Microsoft.EntityFrameworkCore.Design
dotnet add package Microsoft.EntityFrameworkCore.Design
Tạo mới 1 migration và update database
dotnet ef migrations add InitialSchema
dotnet ef database update
Setup API Controller
Bạn thêm MoviesController như sau:[Route("api/[controller]")]
[ApiController]
public class MoviesController : ControllerBase
{
private readonly MyMovieContext _movieContext;
public MoviesController(MyMovieContext movieContext)
{
_movieContext = movieContext;
}
// GET: api/<ValuesController>
[HttpGet]
public List<MovieViewModel> Get()
{
var result = _movieContext.Movies.Select(t=> new MovieViewModel
{
Id = t.Id,
Title = t.Title,
Genre=t.Genre,
ReleaseDate=t.ReleaseDate
}).ToList();
return result;
}
// POST api/<ValuesController>
[HttpPost]
public void Post([FromBody] MoviePostModel movie)
{
if (ModelState.IsValid)
{
var newMovie = new Movie
{
Title = movie.Title,
Genre = movie.Genre,
ReleaseDate = movie.ReleaseDate
};
_movieContext.Movies.Add(newMovie);
_movieContext.SaveChanges();
}
}
}
Từ Controller, hệ thống sẽ gọi trực tiếp xuống Entity Framework: Controller -> EF Core -> Database
Chúng ta sẽ thêm Repository ở giữa: Controller -> Repository -> EF Core -> Database
Một Repository cơ bản sẽ gồm 4 hàm: Get, Insert, Update, Delete.
Một vấn đề đặt ra là có rất nhiều Entity trong hệ thống: Movie, Genre, Author, … Chúng ta sẽ có tương ứng các Repository: MovieRepository, GenreRepository, AuthorRepository với 4 hàm Get, Insert, Update, Delete.
Để tránh lặp lại code, chúng ta sẽ sử dụng Generic Repository.
Generic Repository
public interface IGenericRepository<T> where T : class
{
T GetById(int id);
IEnumerable<T> GetAll();
IEnumerable<T> Find(Expression<Func<T, bool>> expression);
void Add(T entity);
void AddRange(IEnumerable<T> entities);
void Remove(T entity);
void RemoveRange(IEnumerable<T> entities);
}
public class GenericRepository<T> : IGenericRepository<T> where T : class
{
protected readonly MyMovieContext _context;
public GenericRepository(MyMovieContext context)
{
_context = context;
}
public void Add(T entity)
{
_context.Set<T>().Add(entity);
}
public void AddRange(IEnumerable<T> entities)
{
_context.Set<T>().AddRange(entities);
}
public IEnumerable<T> Find(Expression<Func<T, bool>> expression)
{
return _context.Set<T>().Where(expression);
}
public IEnumerable<T> GetAll()
{
return _context.Set<T>().ToList();
}
public T GetById(int id)
{
return _context.Set<T>().Find(id);
}
public void Remove(T entity)
{
_context.Set<T>().Remove(entity);
}
public void RemoveRange(IEnumerable<T> entities)
{
_context.Set<T>().RemoveRange(entities);
}
}
Kế thừa và mở rộng Generic Repository
Thường thì mình sẽ viết những logic riêng trong Business Logic Layer. Nhưng có 1 số logic đơn giản thì bạn có thể bổ sung vào RepositoryVới MovieRepository, chúng ta sẽ giữ nguyên:
public interface IMovieRepository: IGenericRepository<Movie>
{
}
public class MovieRepository:GenericRepository<Movie>, IMovieRepository
{
public MovieRepository(MyMovieContext context) : base(context)
{
}
}
Và mở rộng ActorRepository
public interface IActorRepository: IGenericRepository<Actor>
{
IEnumerable<Actor> GetPopularActors(int count);
}
public class ActorRepository : GenericRepository<Actor>, IActorRepository
{
public ActorRepository(MyMovieContext context) : base(context)
{
}
public IEnumerable<Actor> GetPopularActors(int count)
{
return _context.Actors.OrderByDescending(d => d.Followers).Take(count).ToList();
}
}
Khai báo Dependency Injection trong Program.cs
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
var services = builder.Services;
services.AddDbContext<MyMovieContext>(options =>
options.UseSqlServer(connectionString));
#region Repositories
services.AddTransient(typeof(IGenericRepository<>), typeof(GenericRepository<>));
services.AddTransient<IActorRepository, ActorRepository>();
services.AddTransient<IMovieRepository, MovieRepository>();
#endregion
Quay trở lại MoviesController, chúng ta sẽ thay thế dbContext bằng Repository
[Route("api/[controller]")]
[ApiController]
public class MoviesController : ControllerBase
{
private readonly IMovieRepository _movieRepository;
private readonly MyMovieContext _movieContext;
public MoviesController(IMovieRepository movieRepository, MyMovieContext movieContext)
{
_movieRepository = movieRepository;
_movieContext = movieContext;
}
// GET: api/<ValuesController>
[HttpGet]
public List<MovieViewModel> Get()
{
var result = _movieRepository.GetAll().Select(t=> new MovieViewModel
{
Id = t.Id,
Title = t.Title,
Genre=t.Genre,
ReleaseDate=t.ReleaseDate
}).ToList();
return result;
}
// POST api/<ValuesController>
[HttpPost]
public void Post([FromBody] MoviePostModel movie)
{
if (ModelState.IsValid)
{
var newMovie = new Movie
{
Title = movie.Title,
Genre = movie.Genre,
ReleaseDate = movie.ReleaseDate
};
_movieRepository.Add(newMovie);
_movieContext.SaveChanges();
}
}
}
Tuy nhiên chúng ta vẫn còn sử dụng MovieContext.
Sử dụng UnitOfWork
Khai báo tất cả Repository trong IUnitOfWork.public interface IUnitOfWork : IDisposable
{
IActorRepository Actors { get; }
IMovieRepository Movies { get; }
int Complete();
}
public class UnitOfWork : IUnitOfWork
{
private readonly MyMovieContext _context;
public IMovieRepository Movies { get; }
public IActorRepository Actors { get; }
public UnitOfWork(MyMovieContext context,
IMovieRepository movieRepository,
IActorRepository actorRepository)
{
this._context = context;
this.Movies = movieRepository;
this.Actors = actorRepository;
}
public int Complete()
{
return _context.SaveChanges();
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_context.Dispose();
}
}
}
Thay thế MovieRepository bằng UnitOfWork
[Route("api/[controller]")]
[ApiController]
public class MoviesController : ControllerBase
{
private readonly IUnitOfWork _unitOfWork;
public MoviesController(IUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
}
// GET: api/<ValuesController>
[HttpGet]
public List<MovieViewModel> Get()
{
var result = _unitOfWork.Movies.GetAll().Select(t=> new MovieViewModel
{
Id = t.Id,
Title = t.Title,
Genre=t.Genre,
ReleaseDate=t.ReleaseDate
}).ToList();
return result;
}
// POST api/<ValuesController>
[HttpPost]
public void Post([FromBody] MoviePostModel movie)
{
if (ModelState.IsValid)
{
var newMovie = new Movie
{
Title = movie.Title,
Genre = movie.Genre,
ReleaseDate = movie.ReleaseDate
};
_unitOfWork.Movies.Add(newMovie);
_unitOfWork.Complete();
}
}
}
Thêm DI vào file Program.cs
services.AddTransient<IUnitOfWork, UnitOfWork>();
Download: MediafireTham khảo
https://medium.com/net-core/repository-pattern-implementation-in-asp-net-core-21e01c6664d7
https://codewithmukesh.com/blog/repository-pattern-in-aspnet-core/#Whats_a_Repository_Pattern
Trả lờiXóaConfig connection before the code line: var app = builder.Build();
Trả lờiXóa