If you like to take a look at previous posts, here they are:
There are few things I’d like to do to evolve the repositories.
- Use Linq for specifications
- Introduce Unit of Work (UoW)
Linq for specifications
I used specification objects to build specifications to create query criterion previously. I had to use specification objects to describe the criteria. This may be OK but when the number of criterion increases, management of these specification classes will be an overhead.
We can use Linq and Expressions in favor of our own explicit specifications. Given the popularity and acceptance of Linq, your data store will most likely support Linq in its API so your expressions in your criteria will play nicely.
I like to first separate the query and update portions of the repository. This allows us to be granular. Separating commands and queries from the repository gives me flexibility. I can inject query into controllers which does nothing but querying the data store and building a view model. I can send commands to a message bus and handler of that command can depend on the IUpdateModel contract to persist. The query side may even be in the cache. This is a way to enable CQS (Command Query Seperation) in your domain.
I made the repository non generic and added type parameters to its methods. This makes injecting repositories easier into dependent components.
There is a linq specifications project at google code if like to use a linq based specification for the previously discussed explicit specification solution in part 3.
Unit Of Work
There are lots of materials you can find on the subject. Here are some links to get started…
One think you should be aware is that most data access tools you use (NHibernate, Linq To Sql, Entity Framwork, etc…) already utilize the unit of work pattern. NHibernate uses ISession and the latter two uses DataContext. They are all “units of work” which tracks changes to entities within its scope.
In this example, I used entity framework for persistence. The domain repository will be using that. The unit of work implementation follows the domain below.
public interface IQueryModel { TEntity Get<TEntity>(Func<TEntity, bool> where) where TEntity : class; IEnumerable<TEntity> GetMany<TEntity>(Func<TEntity, bool> where) where TEntity : class; IEnumerable<TEntity> GetAll<TEntity>() where TEntity : class; } public interface IUpdateModel { void Save<TEntity>(TEntity dto) where TEntity : class; void Update<TEntity>(TEntity entity) where TEntity : class; void Delete<TEntity>(Func<TEntity, bool> predicate) where TEntity : class; void Delete<TEntity>(TEntity entity) where TEntity : class; } public interface IRepository : IQueryModel, IUpdateModel { } public interface IPersistData { void Persist(); IPersistData New(); } // Entity framework dependent data persister public class EfDataContext : DbContext, IPersistData { public DbSet<User> Users { get; set; } public EfDataContext() : base("RepositorySeries") { } #region Implementation of IPersistData public void Persist() { SaveChanges(); } public IPersistData New() { return new EfDataContext(); } #endregion } // simple entity public class User { [Key] public int UserID { get; set; } public string FullName { get; set; } } // Entity framework dependent repository public class DomainRepository : IRepository { private DbContext m_db; public DomainRepository(DbContext db) { m_db = db; } public DomainRepository() { m_db = new EfDataContext(); } #region Implementation of IQueryModel public TEntity Get<TEntity>(Func<TEntity, bool> where) where TEntity : class { return m_db.Set<TEntity>().FirstOrDefault(where); } public IEnumerable<TEntity> GetMany<TEntity>(Func<TEntity, bool> where) where TEntity : class { return m_db.Set<TEntity>().Where(where); } public IEnumerable<TEntity> GetAll<TEntity>() where TEntity : class { return m_db.Set<TEntity>(); } #endregion #region Implementation of IUpdateModel public void Save<TEntity>(TEntity dto) where TEntity : class { m_db.Set<TEntity>().Add(dto); } public void Update<TEntity>(TEntity entity) where TEntity : class { m_db.Entry(entity).State = EntityState.Modified; } public void Delete<TEntity>(Func<TEntity, bool> predicate) where TEntity : class { var entity = m_db.Set<TEntity>().FirstOrDefault(predicate); m_db.Set<TEntity>().Remove(entity); } public void Delete<TEntity>(TEntity entity) where TEntity : class { m_db.Set<TEntity>().Remove(entity); } #endregion #region Implementation of IRepository public void Commit() { m_db.SaveChanges(); } public void Rollback() { m_db = new EfDataContext(); } #endregion } public interface IUnitOfWork { IRepository Repository { get; } void Commit(); void Rollback(); } public class UnitOfWork : IUnitOfWork { private IPersistData m_db; public UnitOfWork(IRepository repository, IPersistData db) { m_db = db; Repository = repository; } #region Implementation of IUnitOfWork public IRepository Repository { get; private set; } public void Commit() { try { m_db.Persist(); } catch (Exception ex) { Rollback(); throw ex; } } public void Rollback() { m_db = m_db.New(); } #endregion } // sample app using all above class Program { static void Main(string[] args) { // get these from container (DI) IRepository repository = new DomainRepository(); IPersistData db = new EfDataContext(); // unit of work object var uow = new UnitOfWork(repository, db); // sample entity var u1 = new User { FullName = "Homer Simpson", }; // add, edit, delete entities on the repository uow.Repository.Save(u1); // commit changes in unit of work uow.Commit(); } }
The interfaces and unit of work implementation does not have any dependency to a data access technology. DomainRepository and EfDataContext have dependencies to Entity Framework in this case. This implementation can be changed to some other data access technology easily.