The Repository Pattern

The Repository Pattern in .NET

The Repository Pattern is a design pattern that abstracts data access, providing a centralized and consistent way to interact with data sources. It is a popular pattern in .NET development, especially for applications using Entity Framework or other ORMs (Object-Relational Mappers). In this chapter, we will explore what the Repository Pattern is, why it is useful, and how to implement it effectively in .NET.

What is the Repository Pattern?

The Repository Pattern is a mediator between the domain and data mapping layers. It abstracts the complexity of data access and encapsulates logic for retrieving, saving, and updating data. By using this pattern, developers can work with high-level abstractions (like business objects) rather than database queries.

Key Characteristics

Why Use the Repository Pattern?

  1. Separation of Concerns: Keeps data access logic separate from business logic.
  2. Code Reusability: Centralizes common data operations, making them reusable.
  3. Flexibility: Makes it easier to swap or modify the data source without affecting the application logic.
  4. Testability: Enables the use of mock repositories for unit testing.

Structure of the Repository Pattern

A typical implementation of the Repository Pattern includes:

  1. Entity Classes: Represent the data model.
  2. Repository Interfaces: Define the contract for data operations.
  3. Repository Implementations: Contain the logic to perform data operations.
  4. Unit of Work (optional): Manages multiple repositories and ensures transactions are completed together.

Example Structure

Data/
    IProductRepository.cs
    ProductRepository.cs
    AppDbContext.cs
Models/
    Product.cs

Implementing the Repository Pattern in .NET

Let’s walk through a simple example to understand how to implement the Repository Pattern in .NET.

Step 1: Create the Entity Class

The entity class represents the data model. For example:

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
}

Step 2: Define the Repository Interface

The repository interface defines the contract for data operations:

public interface IProductRepository
{
    IEnumerable<Product> GetAll();
    Product GetById(int id);
    void Add(Product product);
    void Update(Product product);
    void Delete(int id);
}

Step 3: Implement the Repository

The repository class implements the interface and contains the logic to interact with the database:

public class ProductRepository : IProductRepository
{
    private readonly AppDbContext _context;

    public ProductRepository(AppDbContext context)
    {
        _context = context;
    }

    public IEnumerable<Product> GetAll()
    {
        return _context.Products.ToList();
    }

    public Product GetById(int id)
    {
        return _context.Products.Find(id);
    }

    public void Add(Product product)
    {
        _context.Products.Add(product);
        _context.SaveChanges();
    }

    public void Update(Product product)
    {
        _context.Products.Update(product);
        _context.SaveChanges();
    }

    public void Delete(int id)
    {
        var product = _context.Products.Find(id);
        if (product != null)
        {
            _context.Products.Remove(product);
            _context.SaveChanges();
        }
    }
}

Step 4: Register the Repository in the DI Container

Register the repository with the Dependency Injection container in Program.cs:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));

builder.Services.AddScoped<IProductRepository, ProductRepository>();

var app = builder.Build();

app.Run();

Benefits of the Repository Pattern

  1. Centralized Data Access: Provides a single location for data access logic.
  2. Abstraction: Decouples the data access layer from the rest of the application.
  3. Testability: Makes it easier to write unit tests by mocking the repository.
  4. Flexibility: Simplifies changes to the data source or ORM.
  5. Reusability: Common data operations can be reused across multiple parts of the application.

Best Practices

  1. Keep Repositories Focused: Each repository should handle one aggregate or entity type.
  2. Use Generic Repositories (if applicable): For common CRUD operations, consider implementing a generic repository.
  3. Avoid Overloading Repositories: Do not include unrelated operations in a single repository.
  4. Leverage Unit of Work (if needed): Use the Unit of Work pattern to manage transactions across multiple repositories.

Conclusion

The Repository Pattern is a powerful tool for abstracting data access in .NET applications. By using this pattern, you can keep your application clean, modular, and easy to test. The examples provided here should help you implement the Repository Pattern effectively, making your applications more maintainable and scalable over time.