In my previous post, I introduced my take on several versions of a repository implementation.
Take 1
We start with a simple, explicit customer repository.
public interface ICustomerRepository
{
Customer GetCustomerByID(string customerID);
IList<Order> GetOrdersByCustomerID(string customerID);
IList<Customer> GetAllCustomers();
void Insert(Customer customer);
void Delete(Customer customer);
void Update(Customer customer);
}
While being explicit, I am putting bunch of methods like “GetCustomerByID” and “GetOrdersByCustomerID”. But what if I want to find customers and orders by different methods such as “GetCustomersByCategory”, “GetCustomerByLocation”, and so on…
public interface ICustomerRepository
{
// Query Methods
Customer GetCustomerByID(string customerID);
Customer GetCustomerByCompanyName(string city);
Customer GetCustomerByCity(string city);
Customer GetCustomerByPostalCode(string city);
IList<Order> GetOrdersByCustomerID(string customerID);
IList<Customer> GetAllCustomers();
// Command Methods
void Insert(Customer customer);
void Delete(Customer customer);
void Update(Customer customer);
}
I can easily end up having many more get methods about orders, details and so on. It won’t be ideal.
Take 2
I can break that big explicit interface into smaller contracts and implement them where necessary. I prefixed the interfaces with query and command to separate the two. Naming is demonstration purposes only, I wouldn’t name them. It is only to make a point…
public interface ICustomerRepositoryQuery
{
Customer GetCustomerByID(string customerID);
Customer GetCustomerByCompanyName(string city);
Customer GetCustomerByCity(string city);
Customer GetCustomerByPostalCode(string city);
IList<Order> GetOrdersByCustomerID(string customerID);
IList<Customer> GetAllCustomers();
}
public interface ICustomerRepositoryCommand
{
void Insert(Customer customer);
void Delete(Customer customer);
void Update(Customer customer);
}
public interface ICustomerRepository
: ICustomerRepositoryQuery, ICustomerRepositoryCommand
{
void Save(Customer customer);
}
Looks like a little bit better but it is still lipstick on pig.
Take 3
I can collapse all the explicit get methods down to one queryable entity collection. The consumer of this repository, most likely a service, can use Linq to Objects to do queries. That will remove the need for multiple get methods.
public interface ICustomerRepository
{
IQueryable<Customer> GetCustomers();
void Insert(Customer customer);
void Delete(Customer customer);
void Update(Customer customer);
}
Looks like an improvement but what did I gain or lose?
Gains
- Simplified repository
- Power of Linq to query in a uniform programming model
- Deferred execution (Linq feature)
Losses (might be a tad bit strong but you get the point)
- We will break the Law Of Demeter by not being explicit. Instead of
repository.GetCustomerByID("ALFKI")I need to dorepository.Customers().Where(c => c.CustomerID.Equals("ALFKI").SingleOrDefault(). - Testing this will be a little bit harder. We can mock or create our own DataContext in the fake repository to test. I could also simply use
.AsQueryable()and returnIQueryableso that is not a big deal.
Take 4
I want to address a few things in the latest repository. First, since I made it simpler by using IQuery, I can make the repository generic.
public interface IRepository<T>
{
IQueryable<T> GetItems();
void Insert(T item);
void Delete(T item);
void Update(T item);
}
Second, I could replace IList<T> GetItems() with IList<T> GetItems(). That would be a start halfway with the “Law of Delimiter” boo boo I did. It wouldn’t achieve a whole lot though. I would use the deferred execution of getting items. For example, if I returned IList<T> instead of IQueryable<T>, this call below would get all the customers from the database into memory first and then filter them.
Notice the generated SQL? There is no where clause to filter data which is not good. The filtering would be in memory after all the record were returned with IList. Imagine you have a million customers and you would get all of them from database into memory to get one customer with id “ALFKI”.
Here is the SQL if we used IQueriable instead:
Now we have the proper SQL so we’ll stick to IQueryable. I didn’t address the law of delimiter though. May be I could achieve that in the service by exposing more explicit behavior. By the way, the assumption is that this service is not public API, it is only used by the components within one system.
If this service was to be public then I would have to limit the number of customers returned over the wire somehow. I can’t let anyone get one million customers anytime. I could enforce authorization on the call.
For example; some users in particular roles would be able call service.GetCustomers(1000000000); and others would only get 100 customers max at a time by service.GetCustomers(100). I could also cache the customers into memory within the service.
public interface ICustomerService
{
IList<Customer> GetCustomers(int count);
}
This deserves another discussion entirely but the point is, there is no silver bullet, remember? Every solution makes sense in its context and we are not looking for “one solution” to rule them all…
Stay tuned…


1 Comment