5

I've implemented the specification pattern with Linq as outlined here https://www.packtpub.com/article/nhibernate-3-using-linq-specifications-data-access-layer

I now want to add the ability to eager load and am unsure about the best way to go about it.

The generic repository class in the linked example:

public IEnumerable<T> FindAll(Specification<T> specification)
{
  var query = GetQuery(specification);
  return Transact(() => query.ToList());
}

public T FindOne(Specification<T> specification)
{
  var query = GetQuery(specification);
  return Transact(() => query.SingleOrDefault());
}

private IQueryable<T> GetQuery(
  Specification<T> specification)
{
  return session.Query<T>()
    .Where(specification.IsSatisfiedBy());
}

And the specification implementation:

public class MoviesDirectedBy : Specification<Movie>
{

 private readonly string _director;

 public MoviesDirectedBy(string director)
 {
   _director = director;
 }

 public override
    Expression<Func<Movie, bool>> IsSatisfiedBy()
 {
   return m => m.Director == _director;
 }
}

This is working well, I now want to add the ability to be able to eager load. I understand NHibernate eager loading can be done by using Fetch on the query.

What I am looking for is whether to encapsulate the eager loading logic within the specification or to pass it into the repository, and also the Linq/expression tree syntax required to achieve this (i.e. an example of how it would be done).

Community
  • 1
  • 1
Simon
  • 1,499
  • 3
  • 17
  • 23

2 Answers2

3

A possible solution would be to extend the Specification class to add:

public virtual IEnumerable<Expression<Func<T, object>>> FetchRelated
{
    get
    {
        return Enumerable.Empty<Expression<Func<T, object>>>();
    }
}

And change GetQuery to something like:

        return specification.FetchRelated.Aggregate(
            session.Query<T>().Where(specification.IsSatisfiedBy()),
            (current, related) => current.Fetch(related));

Now all you have to do is override FetchRelated when needed

public override IEnumerable<Expression<Func<Movie, object>>> FetchRelated
{
    get
    {
        return new Expression<Func<Movie, object>>[]
                     {
                         m => m.RelatedEntity1,
                         m => m.RelatedEntity2
                     };
    }
}

An important limitation of this implementation I just wrote is that you can only fetch entities that are directly related to the root entity.

An improvement would be to support arbitrary levels (using ThenFetch), which would require some changes in the way we work with generics (I used object to allow combining different entity types easily)

Diego Mijelshon
  • 52,548
  • 16
  • 116
  • 154
  • That's certainly a solution, but that's a mixing of responsibilities (encapsulating querying and fetching strategies). This is why the repository pattern is weak. – jason Dec 06 '10 at 16:27
  • True, but you can always separate the fetching strategies into another class, or make FetchRelated a property that can be set by a service layer. – Diego Mijelshon Dec 06 '10 at 16:54
  • @Diego Mijelshon: What are we gaining from that extra level of abstraction? – jason Dec 07 '10 at 15:14
  • @Jason what are we losing with this approach? :-) my point is, you can separate it however you want; the advantage of putting it in the Specification is that no interfaces change. – Diego Mijelshon Dec 07 '10 at 16:02
  • @Diego Mijelshon: More code to design, develop, test and maintain. That has a cost including but not limited to opportunity cost. – jason Dec 07 '10 at 16:05
  • @Deigo Mijelshon: Is some form of string-based expand capability still available in the new linq provider? Or has that been completely replaced by fetch/fetchmany? – DanP Dec 07 '10 at 17:21
  • @DanP only statically-compiled methods are available (those in NHibernate.Linq.EagerFetchingExtensionMethods) – Diego Mijelshon Dec 07 '10 at 18:10
  • @Diego Mijelshon: Thanks for the confirmation; another similar question...how does one specify a result transformer for a linq query now? – DanP Dec 07 '10 at 18:47
  • @DanP I don't think you can, but you can always use Linq-to-objects after the NH query is executed (i.e. right after `ToList()`); it's more standard and powerful than result transformers (which come from Java and existed way before we had Linq) – Diego Mijelshon Dec 07 '10 at 19:21
  • @Diego Mijelshon: What about the distinct root entity transformer? isn't that rather handy when doing fetchmany scenarios, etc? – DanP Dec 07 '10 at 19:25
  • @DanP `queryWithFetchMany.ToList().Distinct()` – Diego Mijelshon Dec 07 '10 at 19:29
  • Thanks Diego that's helpful. I do need to support fetching muliple levels, can you provide any guidance on how generics can be used to do this? – Simon Dec 22 '10 at 06:01
1

You wouldn't want to put the Fetch() call into the specification, because it's not needed. Specification is just for limiting the data that can then be shared across many different parts of your code, but those other parts could have drastically different needs in what data they want to present to the user, which is why at those points you would add your Fetch statements.

Darren Kopp
  • 76,581
  • 9
  • 79
  • 93