Use the Repository pattern in .Net core

  1. How to setup VS Code environment for ASP.NET Core Project
  2. Use code first to connect database in .Net Core API
  3. Use the Repository pattern in .Net core
  4. Create .Net Core Api controller for CRUD with repository
  5. Use the Serilog in .Net Core
  6. Create Angular Project and Communicate with .Net Core
  7. Create Paging Data from .Net Core Api
  8. How to do Unit Testing for the Core Api Project with Repository

In this post, I will talking about the Repository pattern and why should we use it.


1. Introduction

The Repository design pattern is a widely-used software design pattern that provides an abstraction layer between the business logic and the persistence layer of an application. It is used to manage the persistence of objects in a database or other data store. It separates the logic that retrieves the data and maps it to the domain model from the business logic that acts on the model. This separation of concerns allows for a more modular and flexible architecture that is easier to maintain and test.

Do you want to be a good trading in cTrader?   >> TRY IT! <<

The Repository pattern typically involves defining a set of interfaces for data access operations, such as create, read, update, and delete (CRUD) operations. These interfaces are then implemented by concrete repository classes that interact with the underlying data store.

By abstracting away the details of the data store, the Repository pattern allows the business logic to interact with the data using a uniform interface, regardless of the specific implementation of the data store. This makes it easier to switch between different data stores or to change the schema of the data store without affecting the rest of the application.

And Repository pattern is commonly used in object-oriented programming, particularly in applications that use an object-relational mapping (ORM) framework to interact with a database. However, it can also be used in other contexts, such as web services or file-based data stores.

Ok, let’s starting to try it!

2. Create Repository

From the last article, we have created an User model and connect to database, so we can create the Repository now.

For separate the logic between data access and business, it will use interface in Repository pattern, so we will create the below interface for User model, it should include the base CRUD operations.

Create a folder name Repositories under MyDemo.Core, and create an Interfaces folder

//  MyDemo.Core/Repositories/Interfaces/IUserRepository.cs

public interface IUserRepository
{
    IQueryable<User> GetAll();
    IQueryable<User> GetWithCondition(Expression<Func<User, bool>> expression);
    void Create(User entity);
    void Update(User entity);
    void Delete(User entity);
}

And then we can create the UserRepository.cs file to implement the interface

//  MyDemo.Core/Repositories/UserRepository.cs

public class UserRepository : IUserRepository
{
    protected ApplicationDbContext _context { get; set; }
    public UserRepository(ApplicationDbContext context)
    {
        _context = context;
    }
    public IQueryable<User> GetAll() => _context.Set<User>().AsNoTracking();
    public IQueryable<User> GetWithCondition(Expression<Func<User, bool>> expression) => 
        _context.Set<User>().Where(expression).AsNoTracking();
    public void Create(User entity) => _context.Set<User>().Add(entity);
    public void Update(User entity) => _context.Set<User>().Update(entity);
    public void Delete(User entity) => _context.Set<User>().Remove(entity);
}

The above codes will run well, but there is a problem in this way, what if there are many modes need to be implement the pattern? and we need to copy these almost same codes again and again? and if we want to change a method, we also need to update many of modes! So the other better way is use the Base Class for avoid the duplicate coding.

We can create an IBaseRepository for handle the common CRUD methods

//  MyDemo.Core/Repositories/Interfaces/IBaseRepository.cs

public interface IBaseRepository<T>
{
    IQueryable<T> GetAll();
    IQueryable<T> GetWithCondition(Expression<Func<T, bool>> expression);
    void Create(T entity);
    void Update(T entity);
    void Delete(T entity);
}

As you can see, we use the generic in the base interface, so that can be handle difference models, and we need to implement it :

//  MyDemo.Core/Repositories/BaseRepository.cs

public class BaseRepository<T> : IBaseRepository<T> where T : class
{
    protected ApplicationDbContext _context { get; set; }
    public BaseRepository(ApplicationDbContext context)
    {
        _context = context;
    }
    public IQueryable<T> GetAll() => _context.Set<T>().AsNoTracking();
    public IQueryable<T> GetWithCondition(Expression<Func<T, bool>> expression) => 
        _context.Set<T>().Where(expression).AsNoTracking();
    public void Create(T entity) => _context.Set<T>().Add(entity);
    public void Update(T entity) => _context.Set<T>().Update(entity);
    public void Delete(T entity) => _context.Set<T>().Remove(entity);
}

Ok, then we need to update the user repository interface

public interface IUserRepository : IBaseRepository<User>
{

}

In the end, we also need to update the implementation

public class UserRepository : BaseRepository<User>, IUserRepository
{
    public UserRepository(ApplicationDbContext context) : base(context) {            
    }
}

Just so simple, right?

We only need to inherit the base repository and will be ok! But please hold on, why we need to create the empty repository class? Why not just use one base class for handle all of the models?

There are two reasons for do that:

  1. We need to identity what model will be use and pass the instance class to the base class, although you also can pass the instance to base class directly, but we suggest create a model class and that will be easy to maintain

  2. Another important reason is maybe there will be other custom methods in a model. For example handle the user access right, this should be just for user model, it’s not a common CRUD method, so we need an user model for handle it, we can add more custom methods

3. Using the Async Programming

3.1. What’s Async Programming

In traditional synchronous programming, each operation is executed sequentially, one after the other. This means that if a long-running operation is encountered, the program will be blocked until the operation completes, which can lead to unresponsive or slow applications. Async programming is a programming paradigm that allows for non-blocking, asynchronous execution of code, enabling programs to perform multiple tasks concurrently without blocking the main thread or waiting for long-running operations to complete.

With async programming, long-running operations are executed in the background, allowing the program to continue executing other operations without waiting for the long-running operation to complete. This is achieved using asynchronous functions and callbacks, which enable non-blocking execution of code.

Async programming is particularly useful in scenarios where programs need to perform I/O operations, such as reading and writing to a file or making network requests. These operations can take a long time to complete, during which the program would be blocked in synchronous programming. With async programming, the program can continue executing other operations while the I/O operation is being performed in the background.

By using async programming, we can avoid performance bottlenecks and enhance the responsiveness of our application. So let’s try to use it!

3.2. Create Async Method

We can create the two async method in base interface

//  MyDemo.Core/Repositories/Interfaces/IBaseRepository.cs

Task<IEnumerable<T>> GetAllAsync();
Task<IEnumerable<T>> GetWithConditionAsync(Expression<Func<T, bool>> expression);
Task<T?> GetItemWithConditionAsync(Expression<Func<T, bool>> expression);

and implement it

//  MyDemo.Core/Repositories/BaseRepository.cs

public async Task<IEnumerable<T>> GetAllAsync() =>  await GetAll().ToListAsync();

public async Task<IEnumerable<T>> GetWithConditionAsync(Expression<Func<T, bool>> expression) =>  
        await GetWithCondition(expression).ToListAsync();

4. Using the Codes

Let’s create an API controller to show how to use the repository. As we need to handle the API result, so it should create a common result object for return it fo client will be better.

4.1. Create Api Result Object

First of all, the API should return the status for determine the process success or not, and then should be include the message for describe the result, in the end we need a data object for return the query data from database, so there are at least 3 fields in the ApiResult object:

//  MyDemo.Core/Data/ApiResult.cs

public class ApiResult<T>
{
    public ApiResult()
    {
        this.Success = true;
    }
    /// <summary>
    /// TRUE if the Api attempt is successful, FALSE otherwise.
    /// </summary>
    public bool Success { get; set; }
    /// <summary>
    /// Api return result message
    /// </summary>
    public string Message { get; set; } = null!;
    /// <summary>
    /// Return the data for Api
    /// </summary>
    public T? Data { get; set; }
}

We use the generic for handle any of models.

4.2. Cerate Api Controller

Create an user Api controller, we can pass the repository object through constructor

[ApiController]
[Route("api/[controller]")]
public class UserController : ControllerBase
{
    private readonly IUserRepository _userRepository;

    public UserController(IUserRepository userRepository)
    {
        this._userRepository = userRepository;
    }
}

And then create a Get method for get user data

[HttpGet("users")]
public async Task<ActionResult<IEnumerable<User>>> GetUsers()
{
    var apiResult = new ApiResult<IEnumerable<User>>();
    try
    {
        //here is how to use the repository method
        apiResult.Data = await _userRepository.GetAllAsync();
        //return the result to client side
        return Ok(apiResult);
    }
    catch (Exception ex)
    {
        //handle the exception
        apiResult.Success = false;
        apiResult.Message = ex.Message;
        return StatusCode(500, apiResult);
    }
}

The above Api controller sample only for demo how to use the Repository, so it’s simple and not complete, I will show you next time for how to create CRUD in the Api controller!

The last import thing, you need to register the repository to service before you use it, add below in program.cs

builder.Services.AddScoped<IUserRepository, UserRepository>();

5. Summarize

We now know what’s Repository pattern and how to create it, and we should use the base class for avoid the duplicate codes, also, we know how to create the Async method, that can be help to improve the performance. In the end, we also created an Api controller to try to get the data with Repository.

That’s it, please let me know if you still have any problems 🙂

Loading

Views: 127
Total Views: 661 ,

Leave a Reply

Your email address will not be published. Required fields are marked *