Understanding and Implementing the Repository Pattern in .NET
mohamed Tayel
Posted on August 29, 2024
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
- Create a new .NET Console application:
dotnet new console -n RepositoryPatternDemo
cd RepositoryPatternDemo
- Add the necessary packages:
dotnet add package Microsoft.EntityFrameworkCore
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
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;");
}
}
}
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();
}
}
}
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);
}
}
}
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})");
}
}
}
}
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.
Posted on August 29, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.