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