In my two part series on Expression Trees, I gave a short overview of how the various static methods in the Expression class can be used to generate lambda expressions on the fly. This opens up an endless world of possibilities. The most practical of which is to generate LINQ to SQL CRUD on the fly. In this series, we'll examine one strategy for incorporating automated LINQ to SQL CRUD operations in your architecture's data access layer.
Let's take a look at the basic structure of DAL.
The Repository Pattern
A Repository mediates between the domain and data mapping layers, acting like an in-memory domain object collection. Client objects construct query specifications declaratively and submit them to Repository for satisfaction. Objects can be added to and removed from the Repository, as they can from a simple collection of objects, and the mapping code encapsulated by the Repository will carry out the appropriate operations behind the scenes. Conceptually, a Repository encapsulates the set of objects persisted in a data store and the operations performed over them, providing a more object-oriented view of the persistence layer. Repository also supports the objective of achieving a clean separation and one-way dependency between the domain and data mapping layers. -- Martin Fowler
In our DAL, the repository pattern will act as the liaison between our objects and the database where they need to be saved to and retrieved from. It is important to note that one of the major advantages of using this pattern is that it allows our domain objects to better achieve persistence ignorance. In other words, it allows our domain objects to have no knowledge of how or where they are saved or retrieved. Persistence ignorance is an admirable goal but its a bit out of the scope of our discussion here. As such, we'll be passing in LINQ to SQL generated DTOs directly into our Repository rather then persistence ignorant POCOs (which makes it less then idea for a real world project). Let's look at our Repository interface and BaseRepository abstract class:
[code:c#]
public abstract class BaseRepository<TEntity> : IRepository<TEntity>
{
private IQuerySource<TDomainObject> queryObject;
private IEntityPersistor persistor;
private DataContextInformation contextInfo;
public BaseRepository(IQueryObject<TDomainObject> queryObject, IEntityPersistor persistor, DataContext context)
{
this.persistor = persistor;
this.queryObject = queryObject;
this.contextInfo = new DataContextInfo(context);
}
#region IRepository<TDomainObject> Members
public TEntity Find<TKey>(TKey id)
{
string primaryKey = contextInfo.FindPrimaryKey(new TDomainObject());
return this.Find<TKey>(primaryKey,id).Single();
}
public IList<TEntity> Find<TKey>(string propertyName, TKey value)
{
this.queryObject.Reset();
this.queryObject.AddCriteria<TKey>(CriteriaFactory.EqualTo<TKey>(propertyName, value));
return this.queryObject.Execute();
}
public IList<TEntity> FindAll()
{
this.queryObject.Reset();
return queryObject.Execute();
}
public void Update(TEntity entity)
{
this.persistor.Update(entity);
}
public void Insert(TEntity entity)
{
this.persistor.Insert(entity);
}
public void Remove(TEntity entity)
{
this.persistor.Delete(entity);
}
#endregion
}
[/code]
There are a couple of noteworthy things here. Our interface has all of the basic CRUD operations you would expect: Find (by primary key method), Find, FindAll, Update, Insert, and Delete. We pass in three objects (to make things really clean we will use a Dependency Injection Framework) to our abstract base class. A Query Object that constructs our "Select" LINQ queries, a Entity Persistor which will use reflection to perform the Save/Delete/Update operations, and our DataContext.
Let's take a quick look at how one might use the base classes.
[code:c#]
public interface IProductRepository:IRepository<Product>
{
Product FindByName(string name);
}
[/code]
[code:c#]
public class ProductRepository:BaseRepository<Product>,IProductRepository
{
public ProductRepository(IQueryObject<Product> queryObject, IEntityPersistor persistor, DataContext context):base(queryObject,persistor,context)
{
}
public Product FindByName(string name)
{
return this.Find("ProductName",name).Single();
}
}
[/code]
By inheriting from BaseRepository, our Product Repository has all of the methods found in IRepository<T>. All we have to do is implement IProductRepository. And as you can see, the BaseRepository has the ability to generate queries with simple where clauses by passing in the property name and the value it is equal to so we can implement the FindByName method without writing a single query.
We might use our Product Repository like this:
[code:c#]
IProductRepository productRepo = ObjectFactory.GetInstance<IProductRepository>(); //get repo with some DI framework
Product p1 = productRepo.Find<int>(1);
Product p2 = productRepo.FindByName("ChilliCheese");
IList<Product> pList = productRepo.FindAll();
[/code]
In the next part of this series, we'll dig into the Query Object patter and actually generate some LINQ queries!