This is part two of a two part series. TheĀ first post covers the thought process that I often go through when implementing the repository design pattern. This post will show a complete implementation.
I want to start by saying that I don’t really have a preference as to whether repositories should be coarse-grained or fine-grained. However, I do find that fine-grained repositories are easier to test because the logic layer is a bit thinner but both are equally as effective. On the flip side, a fine-grained repository means your contract is constantly changing because the interface must be updated with each new change so it easier to implement. Pick your poison.
First, let’s go through a fine-grained implementation.
public class CustomerRepository : ICustomerRepository { private OrderTrackerDbContext _context = null; public CustomerRepository() { _context = new OrderTrackerDbContext(); } public IQueryable<Customer> GetAllCustomers() => _context.Customers; public Customer GetCustomerByID(int id) => GetAllCustomers().SingleOrDefault(c => c.ID == id); public Customer GetCustomerByName(string name) => GetAllCustomers().FirstOrDefault(c => c.Name == name); public void Add(Customer customer) { _context.Customers.Add(customer); SaveChanges(); } public void AddRange(List<Customer> customers) { _context.Customers.AddRange(customers); SaveChanges(); } public void Delete(Customer customer) { _context.Customers.Remove(customer); SaveChanges(); } public void DeleteRange(List<Customer> customers) { _context.Customers.RemoveRange(customers); SaveChanges(); } public void Update(Customer customer) { _context.Entry<Customer>(customer).State = EntityState.Modified; SaveChanges(); } private void SaveChanges() { _context.SaveChanges(); } }
Note that there are several different GetCustomerxxx methods. This makes the repository more verbose but, like I mentioned earlier, easier to test. This will also keep your business logic layer a bit cleaner because you don’t have to construct where clauses but it comes at the expense of having sometimes enormous contracts.
Now, for the coarse-grained implementation.
public class OrderRepository : IOrderRepository { private OrderTrackerDbContext _context = null; public OrderRepository() { _context = new OrderTrackerDbContext(); } public IQueryable<Order> GetOrders(Expression<Func<Order, bool>> predicate) => _context.Orders.Where(predicate); public void Add(Order order) { _context.Orders.Add(order); _context.SaveChanges(); } public void AddRange(List<Order> orders) { _context.Orders.AddRange(orders); _context.SaveChanges(); } public void Update(Order order) { _context.Entry<Order>(order).State = EntityState.Modified; _context.SaveChanges(); } public void Delete(Order order) { _context.Orders.Remove(order); _context.SaveChanges(); } public void DeleteRange(List<Order> orders) { _context.Orders.RemoveRange(orders); _context.SaveChanges(); } }
This is a somewhat simpler approach. Only the bare minimum is there. In order to really compare apples to apples, I am including the OrderLogic class here as well. It only seems fair. You have to be fine-grained at some point and the logic class is where it happens with this second implmentation.
public class OrderLogic { private IOrderRepository _orderRepo = null; public OrderLogic(IOrderRepository orderRepo) { _orderRepo = orderRepo; } public IQueryable<Order> GetCustomerOrders(int customerId) => _orderRepo.GetOrders(o => o.CustomerID == customerId); public IQueryable<Order> GetCustomerOrdersForDateRange(int customerId, DateTime startDate, DateTime endDate) => GetCustomerOrders(customerId).Where(o => o.OrderDate >= startDate && o.OrderDate <= endDate); public IQueryable<Order> GetCustomerOrdersForYear(int customerId, int year) { DateTime startDate = DateTime.Parse($"1/1/{year}"); DateTime endDate = DateTime.Parse($"1/1/{year + 1}").AddMilliseconds(-1); return GetCustomerOrdersForDateRange(customerId, startDate, endDate); } public Order GetOrderByID(int orderId) => _orderRepo.GetOrders(o => o.ID == orderId).FirstOrDefault(); }
There you have it. Two different approaches to the same design pattern. Hopefully, you will learn something from this post, have a better understanding of how to go about the implementation, and will create cleaner applications in the future. The repository design pattern is very popular for a reason, it is super simple to implement. Have fun!
Comments 1
Pingback: Repository Design Pattern in C# Part 1 | JasonBentley.net Blog