Chuyển đến nội dung chính

Repository Pattern và Unit of Work

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 Repository

Vớ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: Mediafire

Tham khảo

https://tedu.com.vn/lap-trinh-aspnet/ket-hop-unit-of-work-va-repository-pattern-trong-aspnet-mvc-37.html

https://medium.com/net-core/repository-pattern-implementation-in-asp-net-core-21e01c6664d7

Nhận xét

  1. https://codewithmukesh.com/blog/repository-pattern-in-aspnet-core/#Whats_a_Repository_Pattern

    Trả lờiXóa
  2. Config connection before the code line: var app = builder.Build();

    Trả lờiXóa

Đăng nhận xét

Bài đăng phổ biến từ blog này

[ASP.NET MVC] Authentication và Authorize

Một trong những vấn đề bảo mật cơ bản nhất là đảm bảo những người dùng hợp lệ truy cập vào hệ thống. ASP.NET đưa ra 2 khái niệm: Authentication và Authorize Authentication xác nhận bạn là ai. Ví dụ: Bạn có thể đăng nhập vào hệ thống bằng username và password hoặc bằng ssh. Authorization xác nhận những gì bạn có thể làm. Ví dụ: Bạn được phép truy cập vào website, đăng thông tin lên diễn đàn nhưng bạn không được phép truy cập vào trang mod và admin.

ASP.NET MVC: Cơ bản về Validation

Validation (chứng thực) là một tính năng quan trọng trong ASP.NET MVC và được phát triển trong một thời gian dài. Validation vắng mặt trong phiên bản đầu tiên của asp.net mvc và thật khó để tích hợp 1 framework validation của một bên thứ 3 vì không có khả năng mở rộng. ASP.NET MVC2 đã hỗ trợ framework validation do Microsoft phát triển, tên là Data Annotations. Và trong phiên bản 3, framework validation đã hỗ trợ tốt hơn việc xác thực phía máy khách, và đây là một xu hướng của việc phát triển ứng dụng web ngày nay.

Tổng hợp một số kiến thức lập trình về Amibroker

Giới thiệu về Amibroker Amibroker theo developer Tomasz Janeczko được xây dựng dựa trên ngôn ngữ C. Vì vậy bộ code Amibroker Formula Language sử dụng có syntax khá tương đồng với C, ví dụ như câu lệnh #include để import hay cách gói các object, hàm trong các block {} và kết thúc câu lệnh bằng dấu “;”. AFL trong Amibroker là ngôn ngữ xử lý mảng (an array processing language). Nó hoạt động dựa trên các mảng (các dòng/vector) số liệu, khá giống với cách hoạt động của spreadsheet trên excel.