Generics on the Data Access Layer


I was playing around with a bit of code last night and thought about the repository again. It was not a deep thought, but as it stuck for awhile, I thought about how the data repository is a nice pattern for truly understanding generics in .NET, as it is very practical.

The Basics of Generics

Before going further, let’s look at generics and the problems they solve. Here is a little snippet of code with both a generic and non-generic version:

SortedList sortedList = new SortedList();
sortedList.Add(1, "one");

SortedList<int, string> sortedList2 = new SortedList<int, string>();
sortedList2.Add(1, "one");

Note that I can easily flip both so the string is the key, as in the following:

SortedList sortedList = new SortedList();
sortedList.Add("one", 1);

SortedList<string, int> sortedList2 = new SortedList<string, int>();
sortedList2.Add("one", 1);

When you read about non-generics, one of the first things talked about is boxing and unboxing. This is true and can affect performance, etc. But one of the more germane issues is whether you catch an error at runtime or compile time. With the generic version, this will not work:

SortedList<string, int> sortedList2 = new SortedList<string, int>();
sortedList2.Add("one", 1);
sortedList2.Add("two", 2.0);

When you attempt to compile the above code, you end up with the error “Argument 2: cannot convert from ‘double’ to ‘int’.” I can change the second generic to a floating point and solve the problem, of course, but 2.0 is not an int, so it fails. In the non-generic..

SortedList sortedList = new SortedList();
sortedList.Add("one", 1);
sortedList.Add("two", 2.0);

… everything just works since the int and double are compatible. With the sorted list, the following is perfectly valid.

SortedList sortedList = new SortedList();
sortedList.Add("one", 1);
sortedList.Add("two", "x");

This is definitely a boxing exercise, as well, as the 1 is boxed as an object, as is the number 2. I cannot make the same code work with SortedList<string, int>.

To the Data Layer

When we think of the data layer, we often think of the idea of a repository and will create a repository for each type we have. In a typical eComm type application, we might have a CartRepository, OrderRepository, CustomerRepository, etc. We might think of these like the following code snippet:

public class Order {}
public class Customer {}
public class ShoppingCart {}
    
public class OrderRepository
{
    public List<Order> GetAll()
    {
    }
    public Order GetById(int id)
    {
    }
    public void Save(Order order)
    {
    }
    public void Remove(Order order)
    {
    }
}

public class CustomerRepository
{
    public List<Customer> GetAll()
    {
    }
    public Customer GetById(int id)
    {
    }
    public void Save(Customer customer)
    {
    }
    public void Remove(Customer customer)
    {
    }
}

public class ShoppingCartRepository
{
    public List<ShoppingCart> GetAll()
    {
    }
    public ShoppingCart GetById(int id)
    {
    }
    public void Save(ShoppingCart shoppingCart)
    {
    }
    public void Remove(ShoppingCart shoppingCart)
    {
    }
}

There is a lot of repeat and it forces us to create individual interfaces for testing (you do use unit testing, right?). Here are the interfaces:

public interface IOrderRepository
{
    List<Order> GetAll();
    OrderGetById(int id);
    voidSave(Orderorder);
    voidRemove(Orderorder);
}
public interface
ICustomerRepository
{
    List<Customer> GetAll();
    CustomerGetById(int id);
    voidSave(Customercustomer);
    voidRemove(Customercustomer);
}
public interface
IShoppingCartRepository
{
    List<ShoppingCart> GetAll();
    ShoppingCart GetById(int id);
    voidSave(ShoppingCart shoppingCart);
    voidRemove(ShoppingCart shoppingCart);
}

With generics, however, we can put all of these in a single interface:

public interface IRepository<T>
    where T : class
{
    List<T> GetAll();
    T GetById(int id);
    void Save(T obj);
    void Remove(T obj);
}

Sweet! But what if one or more of the tables use Guids for the primary key (id)? Easy again:

public interface IRepository<T, U>
    where T : class 
    where U : struct
{
    List<T> GetAll();
    T GetById(U id);
    void Save(T obj);
    void Remove(T obj);
}

Now we are cooking with gas. Here is an implementation with a Guid as the primary key:

public class OrderRepository : IRepository<Order, Guid>
{
    public List<Order> GetAll()
    {
    }

    public Order GetById(Guid id)
    {
    }

    public void Save(Order obj)
    {
    }

    public void Remove(Order obj)
    {
    }
}

This is a bit dangerous, however, as I might need a lookup for customer on their customer id, which is not the primary key. Changing the generic interface does not work in this instance. So, we end up with this:

public interface IRepository<T, U>
    where T : class 
    where U : struct
{
    List<T> GetAll();
    T GetById(U id);
    void Save(T obj);
    void Remove(T obj);
}

public interface ICustomerRepository : IRepository<Customer, Guid>
{
    Customer GetAllByCustomerId(string lastName);
}

public class CustomerRepository : ICustomerRepository
{
    public Customer GetAllByCustomerId(string lastName)
    {
    }

    public List<Customer> GetAll()
    {
    }

    public Customer GetById(Guid id)
    {
    }

    public void Save(Customer obj)
    {
    }

    public void Remove(Customer obj)
    {
    }
}

This is very testable goodness, as I can test at any interface and be sure the code is working correctly. And, with proper tests, I have a safety net for refactoring the code further. If you want to refactor to the nth, you can even remove the generic repository bits from the CustomerRepository to a generic repository class. I cover this before (click this link), so I am not going to hit it here. Have fun coding!

Peace and Grace,
Greg

Twitter: @gbworld

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: