Understanding and Implementing the Repository Pattern in .NET

moh_moh701

mohamed Tayel

Posted on August 29, 2024

Understanding and Implementing the Repository Pattern in .NET

In modern software development, clean and maintainable code is paramount. The Repository Pattern is one of the design patterns that help achieve this goal by abstracting the data access layer from the rest of the application, making your code more modular, testable, and easy to maintain.

What is the Repository Pattern?

The Repository Pattern is a design pattern that mediates between the domain and data mapping layers, such as Entity Framework (EF), by encapsulating the logic required to access data sources. It provides a centralized place to perform data operations, making it easier to manage and test the data access code.

Key Benefits:

  • Separation of Concerns: Keeps the business logic separate from the data access logic.
  • Testability: Makes it easier to mock and unit test the data access layer.
  • Centralized Data Access: Provides a consistent way to access data across the application.

Implementing the Repository Pattern in .NET

To demonstrate the Repository Pattern in action, we’ll create a simple .NET Console application that performs basic CRUD (Create, Read, Update, Delete) operations on a Product entity.

Step 1: Set Up the Console Application

  1. Create a new .NET Console application:
   dotnet new console -n RepositoryPatternDemo
   cd RepositoryPatternDemo
Enter fullscreen mode Exit fullscreen mode
  1. Add the necessary packages:
   dotnet add package Microsoft.EntityFrameworkCore
   dotnet add package Microsoft.EntityFrameworkCore.SqlServer
Enter fullscreen mode Exit fullscreen mode

Step 2: Create the Entity and DbContext

First, define the Product entity and the AppDbContext:

using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

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

    public class AppDbContext : DbContext
    {
        public DbSet<Product> Products { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=ProductDb;Trusted_Connection=True;");
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 3: Create the Repository Interface and Implementation

Define the IRepository interface and implement it in the Repository class:

namespace RepositoryPatternDemo
{
    public interface IRepository<T> where T : class
    {
        Task<T> GetByIdAsync(int id);
        Task<IEnumerable<T>> GetAllAsync();
        Task AddAsync(T entity);
        void Update(T entity);
        void Delete(T entity);
    }

    public class Repository<T> : IRepository<T> where T : class
    {
        protected readonly AppDbContext _context;

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

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

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

        public async Task AddAsync(T entity)
        {
            await _context.Set<T>().AddAsync(entity);
            await _context.SaveChangesAsync();
        }

        public void Update(T entity)
        {
            _context.Set<T>().Update(entity);
            _context.SaveChanges();
        }

        public void Delete(T entity)
        {
            _context.Set<T>().Remove(entity);
            _context.SaveChanges();
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 4: Create the Service Layer

Create a ProductService class that uses the repository to interact with the Product entity:

namespace RepositoryPatternDemo
{
    public class ProductService
    {
        private readonly IRepository<Product> _productRepository;

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

        public async Task<Product> GetProductById(int id)
        {
            return await _productRepository.GetByIdAsync(id);
        }

        public async Task<IEnumerable<Product>> GetAllProducts()
        {
            return await _productRepository.GetAllAsync();
        }

        public async Task AddProduct(Product product)
        {
            await _productRepository.AddAsync(product);
        }

        public void UpdateProduct(Product product)
        {
            _productRepository.Update(product);
        }

        public void DeleteProduct(Product product)
        {
            _productRepository.Delete(product);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 5: Implement the Main Method

Finally, implement the main program logic to demonstrate how to use the ProductService to add, retrieve, update, and delete Product entities:

using System;
using System.Threading.Tasks;

namespace RepositoryPatternDemo
{
    class Program
    {
        static async Task Main(string[] args)
        {
            using var context = new AppDbContext();
            var productRepository = new Repository<Product>(context);
            var productService = new ProductService(productRepository);

            // Add a new product
            var newProduct = new Product { Name = "Laptop", Price = 1200.00m };
            await productService.AddProduct(newProduct);
            Console.WriteLine("Product added.");

            // Get all products
            var products = await productService.GetAllProducts();
            Console.WriteLine("Products in the database:");
            foreach (var product in products)
            {
                Console.WriteLine($"- {product.Name} (${product.Price})");
            }

            // Update a product
            newProduct.Price = 1000.00m;
            productService.UpdateProduct(newProduct);
            Console.WriteLine("Product updated.");

            // Get product by ID
            var fetchedProduct = await productService.GetProductById(newProduct.Id);
            Console.WriteLine($"Fetched product: {fetchedProduct.Name} (${fetchedProduct.Price})");

            // Delete a product
            productService.DeleteProduct(fetchedProduct);
            Console.WriteLine("Product deleted.");

            // Check the remaining products
            products = await productService.GetAllProducts();
            Console.WriteLine("Remaining products in the database:");
            foreach (var product in products)
            {
                Console.WriteLine($"- {product.Name} (${product.Price})");
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

The Repository Pattern is a powerful tool in a .NET developer's arsenal, providing a clear and maintainable way to handle data access. By abstracting the data operations, it promotes clean code architecture, making your application easier to manage, extend, and test. This example demonstrates how to implement the Repository Pattern in a simple .NET Console application, showcasing the benefits of a decoupled and testable data access layer.

💖 💪 🙅 🚩
moh_moh701
mohamed Tayel

Posted on August 29, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related