Question

I have taken over a project from a collegue, who uses the IRepository pattern. I have never used it before, so I have some problems understanding how to make a WHERE clause or an ANY clause.

Previously I had the following code, which uses the DataContext repository (the actual implementation, where I can use where clauses:

        IQueryable<Office> officeList = repository.Offices;

        if (specification.CountryId > 0)
        {
            officeList = repository.Offices.Where(c => c.CountryId == specification.CountryId);
        }
        if (specification.LetterSize != null)
        {
            officeList =
                officeList.Where(
                    c => c.OfficeProducts.Any(d => d.OfficeProductDetail.Size == (int)specification.LetterSize));
        }

        return officeList.ToList();

I would like to understand how to get a code snippet like the one above to work using the IRepository pattern. I have tried to make an implementation of a WHERE/QUERY, but could not get it to work.

My question:

  • How do you implement a WHERE/ANY statement in practice, so I can do something like the code snippet above?

My IRepository:

public interface IRepository
    {
        T GetById<T>(long id) where T : class;
        void Create<T>(T entity) where T : class;
        void Update<T>(T entity) where T : class;
        void SaveOrUpdate<T>(T entity) where T : class;
        void Delete<T>(T entity) where T : class;

        IList<T> FindAll<T>(params OrderBy[] orders) where T : class;
        int Count<T>(Expression<Func<T, bool>> whereExpression) where T : class;
        bool Exists<T>(Expression<Func<T, bool>> whereExpression) where T : class;

        T FindFirst<T>(Expression<Func<T, bool>> whereExpression, params OrderBy[] orders) where T : class;
        PaginatedResult<T> Find<T>(Expression<Func<T, bool>> whereExpression, int pageIndex, int pageSize, params OrderBy[] orders) where T : class;

        void ExecuteNativeSQL(string sql);
    }

The implementation:

public class EFRepository : IRepository
    {
        private IDBFactory databaseFactory;
        private LetterAmazerContext dataContext;

        public EFRepository(IDBFactory databaseFactory)
        {
            this.databaseFactory = databaseFactory;
        }

        protected LetterAmazerContext DataContext
        {
            get { return dataContext ?? (dataContext = databaseFactory.Get()); }
        }

        public T GetById<T>(long id) where T : class
        {
            IDbSet<T> dbset = DataContext.Set<T>();
            return dbset.Find(id);
        }

        public void Create<T>(T entity) where T : class
        {
            IDbSet<T> dbset = DataContext.Set<T>();
            dbset.Add(entity);
        }

        public void Update<T>(T entity) where T : class
        {
            dataContext.Entry(entity).State = EntityState.Modified;
        }

        public void SaveOrUpdate<T>(T entity) where T : class
        {
            throw new NotImplementedException();
        }

        public void Delete<T>(T entity) where T : class
        {
            IDbSet<T> dbset = DataContext.Set<T>();
            dbset.Remove(entity);
        }

        public IList<T> FindAll<T>(params OrderBy[] orders) where T : class
        {
            IDbSet<T> dbset = DataContext.Set<T>();
            var query = dbset.Where(t => true);
            query = ApplyOrders<T>(query, orders);
            return query.ToList<T>();
        }

        public int Count<T>(Expression<Func<T, bool>> whereExpression) where T : class
        {
            IDbSet<T> dbset = DataContext.Set<T>();
            return dbset.Count<T>(whereExpression);
        }

        public bool Exists<T>(Expression<Func<T, bool>> whereExpression) where T : class
        {
            IDbSet<T> dbset = DataContext.Set<T>();
            return dbset.Count<T>(whereExpression) != 0;
        }

        public T FindFirst<T>(Expression<Func<T, bool>> whereExpression, params OrderBy[] orders) where T : class
        {
            IDbSet<T> dbset = DataContext.Set<T>();
            var query = dbset.Where(whereExpression);
            query = ApplyOrders<T>(query, orders);
            return query.SingleOrDefault<T>();
        }

        public PaginatedResult<T> Find<T>(Expression<Func<T, bool>> whereExpression, int pageIndex, int pageSize, params OrderBy[] orders) where T : class
        {
            IDbSet<T> dbset = DataContext.Set<T>();
            PaginatedResult<T> results = new PaginatedResult<T>();
            var query = dbset.AsExpandable().Where(whereExpression);
            query = ApplyOrders<T>(query, orders);
            results.Results = query.Skip<T>(pageIndex * pageSize).Take<T>(pageSize).ToList<T>();
            results.TotalItems = dbset.AsExpandable().LongCount(whereExpression);
            return results;
        }

        public void ExecuteNativeSQL(string sql)
        {
            DataContext.Database.ExecuteSqlCommand(sql);
        }

        private IQueryable<T> ApplyOrders<T>(IQueryable<T> query, params OrderBy[] orders)
        {
            if (orders == null || orders.Length == 0) return query;
            foreach (var order in orders)
            {
                query = query.OrderBy(order.ToString());
            }
            return query;
        }
    }
Was it helpful?

Solution

Your repository is at the moment open for arbitrary expressions, including potentially expressions that cannot be evaluated by the implementation.

On one hand this is a potential risk of not being able to deliver an implementation that matches so open contract.

On the other hand, why don't you just then expose it a bit more:

public interface IRepository
{
    ...
    IQueryable<T> Query<T>();
}

and

public class EFRepository : IRepository
{
   ...
   public IQueryable<T> Query<T>()
   {
       return DataContrxt.Set<T>();
   }
}

Note that if you decide to go that way, most of your specific query methods doesn't make sense anymore as such open generic queryable interface satisfies most needs.

Note also that the same concern applies there, if you have multiple implementations you can't possibly guarantee that the contract is satisfied in the same way. Also, your potential client can easily create a query the provider will reject. If you accept these issues, the proposed solution solves your issue as you can now query the repository for almost anything.

And the last note, if you don't plan to have multiple implementations, remove the repository layer completely. The EF context is a unit of work with repositories inside. Starting from version 6, the context can be mocked so unit testing is possible.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top