Last post showed how to use a ObjectContext derivation to automatically generate audit information. Now instead of using a ObjectContext directly, a rather different approach, a pattern called Repository Pattern is used to encapsulate access to the entities.

I’m using the bits included in Visual Studio 2010 Beta 2 with additional features included in Entity Framework 4 CTP 2 (not included in VS 2010) which you can download here.

Specification Pattern

What is Specification Pattern and what does it have to do with Repository Pattern? In simple terms, a specification is a piece of business logic that specifies if an entity is matching a certain criteria. Let’s suppose we need to fetch list of high profile Customers. We can hard code this criteria in UI code behind which is a bad practice you probably agree. We can also place this on our entity class, the Customer. This may not seem so bad. Maybe it is even a good practice to build the business logics like these into the entity, right? But this will only lead to a polluted interface on our entity class as more business logic is introduced. Using Specification Pattern and Separation of Concerns, these pieces of business logic can be placed in a separate class where they belong. Later, we’ll pass this criteria as a parameter into the repository to specify which entities we want to be returned.

The Specification interface we’re going to use would look like this:

1
2
3
4
public interface ISpecification<TEntity> where TEntity : EntityBase
{
Expression<Func<TEntity, bool>> IsSatisfied { get; }
}

…and implementation for our example above would be this:

1
2
3
4
5
6
7
8
9
10
public class HighProfileCustomersSpecification : ISpecification<Customer>
{
public Expression<Func<Customer, bool>> IsSatisfied
{
get
{
return c => c.Orders.Count > 50;
}
}
}

Generic Repository

First let’s specify an interface for our repository. We need generic method like FindByKey, Save, Update and methods that accept an ISpecification parameter like FindOne and FindMany.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public interface IRepository<TEntity> where TEntity : EntityBase
{
/// <summary>
/// Saves the transient entity.
/// </summary>
TEntity Save(TEntity entity);

/// <summary>
/// Updates the existing entity.
/// </summary>
TEntity Update(TEntity entity);

/// <summary>
/// Finds the entity by its key.
/// </summary>
TEntity FindByKey(long id);

/// <summary>
/// Finds entities based on provided criteria.
/// </summary>
IList<TEntity> FindMany(ISpecification<TEntity> criteria);

/// <summary>
/// Finds one entity based on provided criteria.
/// </summary>
TEntity FindOne(ISpecification<TEntity> criteria);
}

Let’s start with the easiest one, FindByKey method. To find an entity an ObjectContext that exposes the entity using IObjectSet<TEntity> is needed so in our generic repository, we’ll create those.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public abstract class Repository<TEntity> : IRepository<TEntity> where TEntity : EntityBase
{
private readonly ObjectContext _context;
private readonly ObjectSet<TEntity> _entity;

protected Repository(ObjectContext context)
{
_context = context;
_entity = Context.CreateObjectSet<TEntity>();
}

protected ObjectContext Context
{
get { return _context; }
}

protected IObjectSet<TEntity> Entity
{
get { return _entity; }
}
}

With this a lot of our implementation would just call the related method on Entity property.

1
2
3
4
5
public TEntity FindByKey(long id)
{
return Entity.Where(x => x.Id == id)
.FirstOrDefault();
}

For Save and Update methods, the ObjectContext does provide a method that has a parameter accepting a fully qualified entity name. What’s FQEN, you may ask? Each object that is in a EntityFramework container has a fully qualified name which is in ContainerName.EntityName format. These are the MetaData information that is exposed through EntitySet property and usually are stored in the .edmx file which we don’t have because we’re using the All Code feature, remember? Fortunately, there is one place that we can get the EntitySet metadata for our POCO entity and that is through the ObjectSet instance.

1
2
3
4
private string GetEntityName()
{
return string.Format("{0}.{1}", _entity.EntitySet.EntityContainer, _entity.EntitySet.Name);
}

Now it is possible to write the generic Save and Update methods as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public TEntity Update(TEntity entity)
{
var fqen = GetEntityName();

Context.ApplyCurrentValues(fqen, entity);
Context.SaveChanges(SaveOptions.AcceptAllChangesAfterSave);

return entity;
}

public TEntity Save(TEntity entity)
{
var fqen = GetEntityName();

Context.AddObject(fqen, entity);
Context.SaveChanges(SaveOptions.AcceptAllChangesAfterSave);

return entity;
}

I’ve wraped the call to SaveChanges in Save and Update methods. You can provide additional SaveChanges method in the repository that will be called explicitly by the code that uses the repository instance.

For remaining query methods that return one or multiple instances of the Entity, we’ll use the generic CreateQuery method on the ObjectContext and feed the ISpecification’s criteria to the Where method.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public TEntity FindOne(ISpecification<TEntity> spec)
{
var entityName = GetEntityName();
var query = _context.CreateQuery<TEntity>(entityName)
.Where(spec.IsSatisfied());

return query.FirstOrDefault();
}

public IList<TEntity> FindMany(ISpecification<TEntity> spec)
{
var entityName = GetEntityName();
var query = _context.CreateQuery<TEntity>(entityName)
.Where(spec.IsSatisfied());

return query.ToList();
}

That sums it up. Our concrete repositories are now just a breeze to create. We can build any missing fine grained methods into our concrete repository using the Entity property. With this approach, you only need one Repository per each Aggregate Root and EF4 will take care of any related transient child entities that should be persisted when aggregate root is saved.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public interface ICustomerRepository : IRepository<Customer>
{
IList<Customer> NewlySubscribed();
}

public class CustomerRepository : RepositoryBase<Customer>, ICustomerRepository
{
public CustomerRepository(ObjectContext context)
: base(context)
{
}

public IList<Customer> NewlySubscribed()
{
var lastMonth = DateTime.Now.Date.AddMonths(-1);
return base.Entity.Where(c => c.Inserted >= lastMonth)
.ToList();
}
}

You can get the whole project containing complete API from here.