Introduction to the Repository Pattern in .NET Core


8 September 2018

In any application that requires data access, it’s crucial to have a well-structured approach to handling data storage and retrieval. Without a clear structure, your application might end up with scattered database-related logic throughout your codebase. This leads to difficulties in maintaining and scaling the application in the long run.

One of the most effective patterns to solve this issue is the Repository Pattern. In this post, we’ll explore how to implement the Repository Pattern in .NET Core to abstract data access, making your code cleaner, more maintainable, and easier to test.


What is the Repository Pattern?

The Repository Pattern is a design pattern that provides a way to encapsulate data access logic, keeping it separate from the business logic in your application. Instead of directly interacting with your database in your application’s services, the Repository Pattern allows you to create a repository class that handles the interaction with your data source. This keeps your code more modular and flexible.

Benefits of Using the Repository Pattern

  • Separation of concerns: Keeps your data access logic separate from the business logic.
  • Testability: Makes unit testing easier by mocking the repository interface.
  • Maintainability: Simplifies the codebase and reduces duplication.
  • Flexibility: Makes it easy to switch out data storage technologies.

How to Implement the Repository Pattern in .NET Core

Let’s dive into how we can implement the Repository Pattern in .NET Core using Entity Framework Core as the data access technology.

Step 1: Define the Repository Interface

The first step is to define a repository interface. This interface will expose methods for the basic CRUD (Create, Read, Update, Delete) operations. By defining the interface, we allow for greater flexibility, as we can easily change the implementation of the repository later.

public interface IRepository<T> where T : class
{
    Task<IEnumerable<T>> GetAllAsync();
    Task<T> GetByIdAsync(int id);
    Task AddAsync(T entity);
    Task UpdateAsync(T entity);
    Task DeleteAsync(int id);
}

In this interface:

  • GetAllAsync retrieves all records of type T.
  • GetByIdAsync retrieves a single entity by its ID.
  • AddAsync adds a new entity to the data source.
  • UpdateAsync updates an existing entity.
  • DeleteAsync deletes an entity by its ID.

Step 2: Implement the Repository

Now that we have our interface, let’s implement it. In the implementation, we’ll use Entity Framework Core to interact with the database.

public class Repository<T> : IRepository<T> where T : class
{
    private readonly DbContext _context;
    private readonly DbSet<T> _dbSet;

    public Repository(DbContext context)
    {
        _context = context;
        _dbSet = _context.Set<T>();
    }

    public async Task<IEnumerable<T>> GetAllAsync()
    {
        return await _dbSet.ToListAsync();
    }

    public async Task<T> GetByIdAsync(int id)
    {
        return await _dbSet.FindAsync(id);
    }

    public async Task AddAsync(T entity)
    {
        await _dbSet.AddAsync(entity);
    }

    public async Task UpdateAsync(T entity)
    {
        _dbSet.Update(entity);
    }

    public async Task DeleteAsync(int id)
    {
        var entity = await _dbSet.FindAsync(id);
        if (entity != null)
        {
            _dbSet.Remove(entity);
        }
    }
}

This implementation does the following:

  • Constructor: Takes a DbContext instance and sets up a DbSet<T> for the entity type T.
  • GetAllAsync: Retrieves all records of type T using ToListAsync().
  • GetByIdAsync: Retrieves an entity by its ID using FindAsync().
  • AddAsync: Adds a new entity using AddAsync().
  • UpdateAsync: Updates an existing entity using Update().
  • DeleteAsync: Deletes an entity by finding it by ID and then removing it using Remove().

Step 3: Register the Repository in Dependency Injection

Once you have the repository class, you need to register it with the dependency injection (DI) container in Startup.cs. This allows you to inject the repository into your controllers or services.

In the ConfigureServices method of your Startup.cs, add:

public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped(typeof(IRepository<>), typeof(Repository<>));
    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}

Here:

  • The AddScoped method registers the repository with the DI container. The typeof(IRepository<>) parameter registers the interface, and typeof(Repository<>) registers the implementation.
  • AddDbContext sets up the database context to interact with the database using SQL Server (you can change it to another provider as needed).

Step 4: Using the Repository in Controllers

Now that the repository is set up and registered, you can inject it into your controllers and use it to perform CRUD operations.

public class ProductController : ControllerBase
{
    private readonly IRepository<Product> _productRepository;

    public ProductController(IRepository<Product> productRepository)
    {
        _productRepository = productRepository;
    }

    [HttpGet]
    public async Task<IActionResult> GetProducts()
    {
        var products = await _productRepository.GetAllAsync();
        return Ok(products);
    }

    [HttpGet("{id}")]
    public async Task<IActionResult> GetProduct(int id)
    {
        var product = await _productRepository.GetByIdAsync(id);
        if (product == null)
        {
            return NotFound();
        }
        return Ok(product);
    }

    [HttpPost]
    public async Task<IActionResult> CreateProduct([FromBody] Product product)
    {
        await _productRepository.AddAsync(product);
        return CreatedAtAction(nameof(GetProduct), new { id = product.Id }, product);
    }

    [HttpPut("{id}")]
    public async Task<IActionResult> UpdateProduct(int id, [FromBody] Product product)
    {
        if (id != product.Id)
        {
            return BadRequest();
        }

        await _productRepository.UpdateAsync(product);
        return NoContent();
    }

    [HttpDelete("{id}")]
    public async Task<IActionResult> DeleteProduct(int id)
    {
        await _productRepository.DeleteAsync(id);
        return NoContent();
    }
}

In this controller, we’re injecting IRepository<Product> to manage CRUD operations for Product entities. The controller handles HTTP requests and delegates the database logic to the repository.


Conclusion

The Repository Pattern helps to organize your data access layer, making it easier to manage and maintain. By abstracting the data access logic into a repository, we achieve separation of concerns, improve testability, and make it easier to swap out the underlying data source if needed.

In this post, we’ve implemented a simple generic repository in .NET Core and shown how it can be used in a controller. By following this pattern, you can create scalable and maintainable applications with clean separation between your business logic and data access.

Stay tuned for more .NET Core tips in future posts!


Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

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