Nov 22, 2011

Implementing Repository with NHibernate

In spite of common now approach of using in memory data base for unit testing NHibernate related code I still do like to have repository. The reason for that is simplicity. In most applications transactions are managed separately, either via action filters or HTTP modules. So in unit test you need to repeat logic not for just creating of object graph, but for saving it also.

What I always wanted is ability to write code like this in tests:

var product = new Product {
    Price = 100,
    Name = "Test"
};

product.Category = new Category {
    Name = "Food"
};

IRepository<Product> products = new List<Product>();

And all the logic for testing queries can be done with LINQ to objects (you will need integration tests to verify real query generated by the ORM). No need for huge test setup and so on.

With release of on NH 3 LINQ provider was greatly improved (but still has a lot of troubles). In this post I’m going to show implementation of Repository described in Fabio’s post. The main idea described there is that IRepository interface should just look like this:

/// <summary>
/// Repository for basic entities persistence actions
/// </summary>
/// <typeparam name="T"></typeparam>
public interface IRepository<T> : ICollection<T>, IQueryable<T>
    where T : Entity
{
    T Get(long id);
}

The only additional method is Get. Its just useful in a lot of cases. Everything else is provided with mentioned interfaces. Implementation from NHibernate point of view is pretty straight forward and I won’t describe it, you can see in the project sample. The interesting part is mocking one. Here is code for one of the methods:

public class FakeRepository<T> : List<T>, IRepository<T>
    where T : Entity
{
    public FakeRepository(IEnumerable<T> products) : base(products)
    {
    }

    public Expression Expression
    {
        get
        {
            return ((IEnumerable<T>)this).Select(x => x).AsQueryable().Expression;
        }
    }
}

So now we can implement test like this:

[Test]
public void Repository_can_be_created_from_simple_list()
{
    Product product = new Product();
    
    List<Product> products = new List<Product>();
    products.Add(product);

    IRepository<Product> repository = new FakeRepository<Product>(products);

    Assert.That(repository, Is.Not.Empty);
}

And it will pass. Also all production code can do any sort of LINQ queries and they will succeed.

The last and probably the most scary thing - fetching. LINQ to NHibernate has Fetch and FetchMany extension methods. But when you use them on regular list exception is thrown:

System.InvalidOperationException : There is no method 'Fetch' on type 'NHibernate.Linq.EagerFetchingExtensionMethods' that matches the specified arguments

So we need to abstract fetching away. In order to do that, we will need our own Fetch and FetchMany methods and IFetchRequest interface with the following signature:

public interface IFetchRequest<TQueried, TFetch> : IOrderedQueryable<TQueried> 
{
}

Instance of this interface will be returned as a result of calls to our new extension methods, that look like this:

public static class EagerFetch
{
    public static IFetchRequest<TOriginating, TRelated> Fetch<TOriginating, TRelated>(this IQueryable<TOriginating> query, Expression<Func<TOriginating, TRelated>> relatedObjectSelector)
    {
        return FetchingProvider().Fetch(query, relatedObjectSelector);
    }

    // ... other methods

    public static Func<IFetchingProvider> FetchingProvider = () => new NhFetchingProvider();
}

I’m showing only method here, others are implemented in the same way (and yes, it is ugly). But the good news is that you write it once, and forget. Interesting part is FetchingProvider that performs fetching itself. The instance of provider is provided by Func, that means that in tests you can easily change provider instance. With such code somewhere in test fixture setup:

EagerFetch.FetchingProvider = () => new FakeFetchingProvider();

Implementation of NHibernate provider is on the github (together with fake provider). FakeProvider in its turn just doing nothing. But in theory we can mock it and set some verifications, but I don’t think it’s a good idea.

Full source code with working solution you can find on the github.

3 comments:

  1. This comment has been removed by the author.

    ReplyDelete
  2. Здравствуйте Андрей! В поисках решения внедрения метода Fetch() в Repository Pattern я наткнулся на ваш блог.Я попытался внедрить ваш пример в свой проект. К сожалению у меня всё ровно появляется ошибка There is no method 'Fetch' on type 'NHibernate.Linq.EagerFetchingExtensionMethods' that matches the specified arguments, указывая на эту линию кода : ... return this.NhFetchRequest.GetEnumerator(); <-- .... итак шаги по которым я следовал:
    1.Заставил IRepository унаследовать от : ICollection, IQueryable
    2.В RepositoryBase :IRepository , прописал конкретную реализацию методов интерфейсов ICollection и IQueryable
    3.Далее я просто скопировал следующие классы в свой проект(дело не только в лени) :
    - IFetchingProvider.cs
    - IFetchRequest.cs
    - NhFetchingProvider.cs
    - FetchRequest.cs
    - EagerFetch.cs
    4. Поменял namespaces
    5. Далее в контроллере MVC3 проекта пробовал использовать метод Fetch() след. образом:
    public ActionResult Index()
    {
    IQueryable <> list= null;
    using(var unitOfWork=_unitOfWorkFactory.Create())
    {
    list=_studentRepository.GetAll().Fetch(s => s.Mark);
    //И Так: list=EagerFetch.Fetch(_studentRepository.GetAll(),s => s.Mark);
    }
    return View("Index",list);
    }
    // Думаю по названию переменных всё ясно.
    6. using NHibernate.linq;в котроллере не добавлял, чтобы избежать конфликтов. Хотя если добавить то код даже не компилируется.

    Прошу если есть время, рассмотреть мою ситуацию... может найдём решение проблемы.Если вам удобнее на английском языке ответьте на нём. Спасибо!

    ReplyDelete