views:

72

answers:

2

I'm following on from a previous question. The answer I accepted involves using a generic IRepository to handle basic CRUD, wrapped with a domain specific IMovieRepository which delegates to the generic setup. A further detail involves having a WrapQueryInSession method on the generic IRepository:

IEnumerable<T> WrapQueryInSession(Func<ISession, IEnumerable<T>> query);

I was getting to implementation when I realized that this exposes the NHibernate ISession to consumers of the generic repository. NHibernate is otherwise fully contained in the IRepository implementation, but for that method signature.

This comes to the fore when I want to unit test MovieRepository, by having an IRepository, implemented in RepositoryFake, passed to the MovieRepository constructor:

protected override void BeforeEachTest()
{
    _fixture = new MovieRepository(new RepositoryFake());
}

My test class has a private fake repository implementation:

private class RepositoryFake : IRepository<Movie>
{
    ...
    public IEnumerable<Movie> WrapQueryInSession(Func<ISession, IEnumerable<Movie>> query)
    {
        ...
    }
    ...
}

The way this is set up, the test class, and any other consumer of an IRepository implementation, is made aware of the ISession from NHibernate, and thus NHibernate itself. This seems a case of a leaky abstraction.

Is there a better way to fully contain use of NHibernate within an IRepository implementation?

+2  A: 

The idea from my answer to your previous question was that the generic IRepository is only known inside your infrastructure layer - it is not published outside of this. When you publish the ISession to the non-generic repositories they gain a very versatile interface, as they have access to the ISession for querying. The problem with not exposing the ISession is that your generic repository will either:

  1. Limit your querying capabilities or
  2. Have a whole host of different methods for querying (basically duplicating the interface of the ISession.

Seems a bit of a waste having NHibernate's querying interface hidden away inside a facade (which the generic Repository would be limited to).

IMO, if you choose nHibernate, you should leverage the power it gives you and live with the dependence through-out your infrastructure dll (including tests). Think of the generic IRepository interface as a helper interface to NHibernate to reduce the amount of duplicate code inside the repositories.

Goblin
I hadn't thought of it that way, but now it starts to make sense. It is like a puzzle: the pieces are being placed and the picture is now forming :)
Grant Palin
On the up side, once I have this nailed down, I should be able to create a template for use in other projects.
Grant Palin
+2  A: 

I agree with the principle that NHibernate should not be abstracted completely but I have found that the querying interface of NHibernate can be hidden away without too much hassle and that can happen by using Query objects.

Each query object should leverage the power the NHibernate provides (ISession, ICriteria, IQuery, etc) and can be executed by the implementation of the IRepository.

Having Query objects instead of methods in your repositories offers better testability and it would not require any references to NHibernate in your test classes.

Here is how the whole thing could look like:

public interface IRepository
{
        ISession Session { get; }
        TResult Query<TResult>(IQuery<TResult> query);
}

public class Repository : IRepository
{
    public ISession Session
    {
        get { return /* Call the session factory to return an ISession */; }
    }

    public TResult Query<TResult>(IQuery<TResult> query)
    {
        return query.Execute(Session));
    }
}

public interface IQuery<TResult>
{
    TResult Execute(QueryContext context);
}

public abstract class Query<TResult> : IQuery<TResult>
{
    public abstract TResult Execute(ISession session);
}

public class GetPeopleByName: IQuery<Person>
{
    private readonly string _name;

    public GetPeopleByName(string name)
    {
        _name = name;
    }

    public override IList<Person> Execute(ISeesion session)
    {
        var query = context.Session.CreateCriteria(typeof(Person))
            .Add(Restrictions.Eq("Name", _name));

        return query.List<Person>();
    }
}

Then you can use the above like:

IRepository repository = /* Get somehow the implementation */
IList<Person> people = repository.Execute(new GetPeopleByName("Anna"));
tolism7