Repository Design Pattern in C# Part 2

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

  1. Pingback: Repository Design Pattern in C# Part 1 | JasonBentley.net Blog

Leave a Reply

Your email address will not be published.