5

I have a standard repository interface in C# which includes the following methods:

IEnumerable<T> GetAll();
T GetById(int id);
void Delete(T entity);
void Add(T entity);

At my domain layer all I am instantiating is a new Unit of Work wrapper and passing it to a repository. The Unit of Work wrapper class hides whether I'm using NHibernate or Entity Framework and exposes a Commit() method.

In my domain layer, how can I query for objects which meet only a particular criteria?

I think what I'm doing at the moment is terribly inefficient. I am currently doing this:

var results = myRepository.GetAll().Where......

If I have a very large amount of objects, is GetAll() going to return every one of them before filtering out the ones I don't need? How can I prevent unwanted object from being returned at all?

Obviously I could add more methods to the interface, but this doesn't seem in keeping with exposing only CRUD operations via the interface.

i.e. - I don't think I should be adding things like (but maybe I'm wrong):

IList<T> GetAllWhereMeetsMyCriteria();
Ladislav Mrnka
  • 360,892
  • 59
  • 660
  • 670
JMc
  • 971
  • 2
  • 17
  • 26

3 Answers3

6

Yes your GetAll().Where will pull all your objects from database to your application and perform linq-to-objects. @Kamyar solution will do the same. @Dysaster has provided a correct solution while I was writing this answer.

First of all - do you really need repository which must support both NHibernate or EF? Is it customer requirement? If not you are wasting resources. Select the technology and create minimal abstraction needed for your application to follow separation of concerns.

If you really need high level abstraction and absolute independence on persistence API you should use the third pattern from the family - Specification pattern. With custom specifications you will be able to change persistence to anything if you translate your conditions described by specification into actions needed on data source. Criteria API in NHibernate or extension methods on IQueryable are specifications but they are technology dependent.

Ladislav Mrnka
  • 360,892
  • 59
  • 660
  • 670
  • Would you please clarify why `this.Query().Where(criteria).ToList();` returns all entities and then performs linq-to-objects? I thought it doesn't fetch the data from database until we use ToList() or equivalent methods.Does `Query.Where` first returns all the data from db and them performs linq? – Kamyar May 14 '11 at 11:22
  • Not a customer requirement. I am writing a project for my MSc in which I examine aspects of the Entity Framework. I was asked to contrast this with a more mature O/RM (NHibernate) to see how they stack up against one another, hence the need to support both. Thanks for your response. – JMc May 14 '11 at 11:23
  • 1
    @Kamyar: `ToList` executes the query but the problem is that you use `Func` in where instead of `Expressoin`. `Func` is delegate used on `IEnumerable` so EF must first load all entities to memory to be able to run the delegate. `Expression` is used on `IQueryable` and it is translated to SQL. This is very common mistake. – Ladislav Mrnka May 14 '11 at 11:50
4

Have a look at the repository pattern used in this blog post [weblogs.asp.net]. I have found the source code to employ a few interesting patterns that I keep returning back to and checking again. The code implements both repository and unit of work patterns.

To answer your specific question, the repository interface contains the method:

IEnumerable<T> GetMany(Expression<Func<T, bool>> where);    

It is implemented as follows in the RepositoryBase<T> class:

public virtual IEnumerable<T> GetMany(Expression<Func<T, bool>> where)
{
    return dbset.Where(where).ToList();
}

Probably both EF and NHibarnate will have the interfaces necessary to support this usage.

vhallac
  • 13,301
  • 3
  • 25
  • 36
2

Not familiar with nhibernate, But usually, I go with your last solution(IList<T> GetAllWhereMeetsMyCriteria();):

public IList<TEntity> Find<TEntity>(Func<TEntity, bool> criteria) where TEntity : class
{
    return this.Query<TEntity>().Where<TEntity>(criteria).ToList<TEntity>();
}  

There's a great Genric Repository written at: http://www.martinwilley.com/net/code/nhibernate/genericrepository.html

You might want to use that since it covers more situations. Also, you can derive your custom repository from it for certain needs. (Example: http://www.martinwilley.com/net/code/nhibernate/productrepository.html)

UPDATE:
You can define custom criterias in your derived repositories and use them. e.g.

private ICriteria CriteriaCategoryId(int categoryId)
    {
        ICriteria criteria = Session.CreateCriteria(typeof(Product));
        criteria.CreateCriteria("Category")
            .Add(Expression.Eq("Id", categoryId));
        return criteria;
    }  
public IList<Product> ProductByCategory(int categoryId, int pageStartRow, int pageSize)
    {
        var criteria = CriteriaCategoryId(categoryId)
            .SetFirstResult(pageStartRow)
            .SetMaxResults(pageSize);

        return criteria.List<Product>();
    }
Kamyar
  • 18,639
  • 9
  • 97
  • 171
  • That looks helpful thanks so +1. I'll look into your suggestion and mark as answer if that turns out to be the case. Thanks! – JMc May 14 '11 at 11:01
  • No problem. Just notice that many people do not use Generic repositories directly. They derive custom repositories from that (e.g. `ProductRepository`) and use these derived ones directly. Good luck. – Kamyar May 14 '11 at 11:05