Thursday, October 1, 2009

Using Interfaces, Generics and the Repository Pattern

I’ve been using the repository pattern to insulate my apps from the LINQ to SQL so that I can easily change when (or if) MS kills L2S. The nice thing about this is that I can write a generic repository the encompasses some of the tasks so that the code is consistent for all repositories.

A little background first. We name all our tables the plural of the objects contained and the first column is an integer primary key named in the singular followed by a “Key” suffix. For example, the Customers table has a first column of CustomerKey. This key name is then used in all related tables as the foreign key column. This makes it very easy to come in behind someone and figure out the data structure.

This convention gets in the way of making life easier for me because all items have a different name for the primary key. To fix this, created an interface called IPrimaryKey, shown below.

public interface IPrimaryKey
{
    int PrimaryKey { get; set; }
}

In my database model, I change the name from CustomerKey to PrimaryKey.

image  image

I then create an interface for Customer and a partial class implementing the ICustomer interface. Note that ICustomer adds IPrimaryKey, so any class implementing ICustomer will also have a PrimaryKey.

public interface ICustomer
     :IPrimaryKey
{
     string FirstName { get; set; }
     string LastName { get; set; }
     string Address { get; set; }
     string City { get; set; }
}

partial class Customer
     : ICustomer
{
}

I can now create an interface for my repository and a generic base class that can be inherited by all my other repositories. Creating an interface allows me to use InversionOfConrol to keep my classes flexible, especially for testing.

public interface IRepository<T> where T : IPrimaryKey
{    
    T GetByKey(int key);    

    void Save(T entity);
}

public abstract class RepositoryBase<T>
    : IRepository<T> where T : IPrimaryKey
{    
    protected BogusDbDataContext dc;
    protected IQueryable<T> SourceTable;    

    public T GetByKey(int key)
    {
        return (from x in SourceTable
                where x.PrimaryKey == key                
                select x).SingleOrDefault();    
    }    

    public IQueryable<T> List()    
    {        
        return from x in SourceTable select x;    
    }    

    public abstract void Save(T entity);
}

And finally here is my customers repository.

public class CustomersRepository
    : RepositoryBase<ICustomer>, ICustomersRepository
{
    public CustomersRepository()
    {
        dc = new BogusDbDataContext();
        SourceTable = dc.Customers;
    }

    public override void Save(ICustomer entity)
    {
        dc.Customers.InsertOnSubmit((Customer)entity);
    }
}

Notice that customers has no code to retrieve an entity by it’s primary key. That code exists in RepositoryBase. All my other repositories can use the same code.

Here is a mock repository and some tests to see how it all fits together.

public class CustomersRepositoryMock
    : RepositoryBase<ICustomer>, ICustomersRepository
{
    private List<ICustomer> customers;

    public CustomersRepositoryMock()
    {
        customers = new List<ICustomer>();

        customers.Add(new Customer() { PrimaryKey = 1, FirstName = "John", LastName = "Public", Address = "123 Main", City = "Somewhereville" });
        customers.Add(new Customer() { PrimaryKey = 2, FirstName = "Larry", LastName = "Jones", Address = "456 Oak", City = "Somewhereville" });
        customers.Add(new Customer() { PrimaryKey = 3, FirstName = "Fred", LastName = "Smith", Address = "789 Grand", City = "Somewhereville" });
        customers.Add(new Customer() { PrimaryKey = 4, FirstName = "Barb", LastName = "Johnson", Address = "1234 Cedar", City = "Somewhereville" });

        this.SourceTable = customers.AsQueryable();
    }

    public override void Save(ICustomer entity)
    {

    }
}
[TestMethod()]
public void GetByKeyTest()
{
    CustomersRepositoryMock repository = new CustomersRepositoryMock();
    int key = 3;
    ICustomer target = repository.GetByKey(key);

    Assert.AreEqual(key, target.PrimaryKey);
}

[TestMethod()]
public void ListTest()
{
    CustomersRepositoryMock repository = new CustomersRepositoryMock();
    IQueryable<ICustomer> target = 
        from x in repository.List()
            where x.LastName.StartsWith("J")
            orderby x.LastName
            select x;

    Assert.AreEqual(2, target.Count());
    Assert.AreEqual("Johnson", target.First().LastName);
    Assert.AreEqual("Jones", target.Last().LastName);
}
The L2S data context needs to be handled appropriately. I treat the repositories as a unit of work in that I dispose of them almost immediately after use. In this way, I don’t get in trouble with load options. I also try to make sure that I return concrete objects except where I use IQueryable methods. This prevents unwanted problems with a data context that no longer exists.

There are other interfaces that can be used such as one for Beginning / Ending dates and IsActive. Again, write the code in RepositoryBase and all the derived repositories can use the same code.

No comments: