Quick start: ASP.Net Core 3.1, Entity Framework Core, CQRS, React JS Series — Part 4: Repository pattern implementation
In this content, repository pattern will be implemented on entity framework db context.
The parent content of this series: Quick start: ASP.Net Core 3.1, Entity Framework Core, CQRS, React JS Series
Outline
- Github feature branch
- Project structure
- Repository pattern implementation (Base and Wrapper)
- Definition of domain based repositories
- What is CQRS?
- Implementing CQRS with MediatR library
- Extensions
- Startup
- Use it in API
- Respone
Github feature branch
Project Structure
Repository folder in Domain and Infrastructure.EF projects are placed.
Repository pattern implementation (Base, Wrapper)
Base definiton and Wrapper are separated.
public abstract class RepositoryBase<T> : IRepositoryBase<T> where T : class
{
protected RepositoryContext RepositoryContext { get; set; }
public RepositoryBase(RepositoryContext repositoryContext)
{
this.RepositoryContext = repositoryContext;
}
public IQueryable<T> FindAll()
{
return this.RepositoryContext.Set<T>().AsNoTracking();
}
public IQueryable<T> FindByCondition(Expression<Func<T, bool>> expression)
{
return this.RepositoryContext.Set<T>()
.Where(expression).AsNoTracking();
}
public void Create(T entity)
{
this.RepositoryContext.Set<T>().Add(entity);
}
public void Update(T entity)
{
this.RepositoryContext.Set<T>().Update(entity);
}
public void Delete(T entity)
{
this.RepositoryContext.Set<T>().Remove(entity);
}
}
public class RepositoryWrapper : IRepositoryWrapper
{
private RepositoryContext _repoContext;
public RepositoryWrapper(RepositoryContext repositoryContext)
{
_repoContext = repositoryContext;
}
private IProductRepository _products;
public IProductRepository Product
{
get
{
if (_products == null)
{
_products = new ProductRepository(_repoContext);
}
return _products;
}
}
private ITagRepository _tags;
public ITagRepository Tag
{
get
{
if (_tags == null)
{
_tags = new TagRepository(_repoContext);
}
return _tags;
}
}
private IProductsTagsRepository _productsInTags;
public IProductsTagsRepository ProductsTags
{
get
{
if (_productsInTags == null)
{
_productsInTags = new Repositories.ProductsTagsRepository(_repoContext);
}
return _productsInTags;
}
}
public async Task SaveAsync()
{
await _repoContext.SaveChangesAsync();
}
}
Definition of domain based repositories
Async GET methods are in placed for domain based repositories
public interface IProductRepository : IRepositoryBase<Product>
{
Task<IEnumerable<Product>> GetAllProductsAsync();
Task<Product> GetProductByIdAsync(Guid productId);
Task<Product> GetProductWithDetailsAsync(Guid productId);
void CreateProduct(Product product);
void UpdateProduct(Product product);
void DeleteProduct(Product product);
}
Implementation:
public class ProductRepository : RepositoryBase<Product>, IProductRepository
{
public ProductRepository(RepositoryContext repositoryContext)
: base(repositoryContext)
{
}
public void CreateProduct(Product product)
{
Create(product);
}
public void DeleteProduct(Product product)
{
Delete(product);
}
public async Task<IEnumerable<Product>> GetAllProductsAsync()
{
return await FindAll()
.OrderBy(p => p.Name)
.ToListAsync();
}
public async Task<Product> GetProductByIdAsync(Guid productId)
{
return await FindByCondition(product => product.Id.Equals(productId))
.FirstOrDefaultAsync();
}
public async Task<Product> GetProductWithDetailsAsync(Guid productId)
{
return await FindByCondition(product => product.Id.Equals(productId))
.Include(ac => ac.ProductsTags)
.ThenInclude(x => x.Tag)
.FirstOrDefaultAsync();
}
public void UpdateProduct(Product product)
{
Update(product);
}
}
What is CQRS?
Command–query separation is a principle of imperative computer programming. It was devised by Bertrand Meyer as part of his pioneering work on the Eiffel programming language. It states that every method should either be a command that performs an action, or a query that returns data to the caller, but not both.
Implementing CQRS with MediatR library
In Domain.Services project
dotnet add package MediatR.Extensions.Microsoft.DependencyInjection
Define Query:
namespace Domain.Services
{
public class GetProductByIdQuery : IRequest<Product>
{
public Guid Id { get; set; }
}
}
Define Query Handler:
public class GetProductByIdQueryHandler : IRequestHandler<GetProductByIdQuery, Product>
{
public ILoggerManager Logger;
public IRepositoryWrapper Repository { get; }
public GetProductByIdQueryHandler(ILoggerManager logger, IRepositoryWrapper repository)
{
Repository = repository;
Logger = logger;
}
public async Task<Product> Handle(GetProductByIdQuery request, CancellationToken cancellationToken)
{
var tag = await Repository.Product.GetProductByIdAsync(request.Id);
Logger.LogInfo($"product returned from database.");
return tag;
}
}
That’s it!
Dependency Injection for your queries, commands and handlers:
public static class DomainServicesExtensions
{
public static void ConfigureMediatR(this IServiceCollection services)
{
services.AddMediatR(typeof(GetAllTagsQueryHandler));
}
}
Startup
public class Startup
{
public Startup(IConfiguration configuration)
{
LoggerServiceExtensions.LoadConfiguration();
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.ConfigureCors();
services.ConfigureIISIntegration();
services.ConfigureLoggerService();
services.ConfigureDbContext(Configuration);
services.ConfigureRepositoryWrapper();
services.ConfigureAutoMapper();
services.ConfigureValidationFilter();
services.ConfigureMediatR();
services.AddControllers().AddNewtonsoftJson(o => o.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore);
services.ConfigureSwagger();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.ConfigureCustomExceptionMiddleware();
app.ConfigureSwaggerMiddleware();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCors("CorsPolicy");
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.All
});
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
Use it in API:
[HttpGet("{id}", Name = "ProductById")]
public async Task<IActionResult> GetProductById(Guid id)
{
var products = await _mediator.Send(new GetProductByIdQuery() { Id = id });
var productsResult = _mapper.Map<ProductDto>(products);
return Ok(productsResult);
}
Response
Conclusion
In this content, Repository implementation, CQRS and API implementation are demonstrated.