NHibernate Linq Eager Loading with generic reposit

2019-04-11 15:26发布

问题:

Currently we are using a generic repository for all our entities that exposes an IQueryable (using NH3 linq support) that is then used by our service layer to construct specific queries.

I now need to eagerly load an association. Is there any way I can expose an IQueryable and pass in optional fetch expressions? The problem I see is that Fetch must come last in the expression (as per http://mikehadlow.blogspot.com/2010/08/nhibernate-linq-eager-fetching.html).

I'm curious as to how others have achieved this.

I did consider perhaps passing Linq specifications to the repository so that these could evaluated prior to calling Fetch. I would however still need some way of passing in the Fetch expressions.

Thanks Ben

回答1:

I use overloads of my FindOne and FindAll repository calls to achieve this..something like:

Function FindOne(ByVal spec As ILinqSpecification(Of T)) As T
Function FindOne(ByVal spec As ILinqSpecification(Of T), ByVal strategy As IFetchingStrategy(Of T)) As T
Function FindAll(ByVal spec As ILinqSpecification(Of T)) As IQueryable(Of T)
Function FindAll(ByVal spec As ILinqSpecification(Of T), ByVal strategy As IFetchingStrategy(Of T)) As IQueryable(Of T)

etc..

Perhaps not the cleanest approach, but it does the job. I'm not certain if this is still an issue with the trunk linq provider or not, but I can also decide whether or not to apply the distinct result transformer to my results in the FindAll scenarios based upon whether or not my fetching strategy contains a collection.

My specification and fetching strategy implementations are based upon those available in the ncommon project.

For reference, my full generic "read-only" repository interface is as follows:

Public Interface IReadOnlyRepositoryWithTypedId(Of T As IEntityWithTypedId(Of IdT), IdT)

    Function LoadById(ByVal id As IdT) As T
    Function GetById(ByVal id As IdT) As T
    Function FindOne(ByVal spec As ILinqSpecification(Of T)) As T
    Function FindOne(ByVal spec As ILinqSpecification(Of T), ByVal strategy As IFetchingStrategy(Of T)) As T
    Function GetCount() As Integer
    Function GetCount(ByVal spec As ILinqSpecification(Of T)) As Integer
    Function HasAny(ByVal spec As ILinqSpecification(Of T)) As Boolean
    Function FindAll(ByVal spec As ILinqSpecification(Of T)) As IQueryable(Of T)
    Function FindAll(ByVal spec As ILinqSpecification(Of T), ByVal strategy As IFetchingStrategy(Of T)) As IQueryable(Of T)
    Function FindAll() As IQueryable(Of T)
    Function FindAll(ByVal strategy As IFetchingStrategy(Of T)) As IQueryable(Of T)

End Interface


回答2:

The solution I came up with in the end was adding the following to my generic repository interface:

public IEnumerable<T> FindAll<TRelated>(Specification<T> specification, Expression<Func<T, TRelated>> fetchExpression);

The NHibernate implementation was:

public IEnumerable<Product> FindAll<TRelated>(Specification<Product> specification, Expression<Func<Product, TRelated>> fetchExpression) {
return session.Query<Product>()
    .Where(specification.IsSatisfiedBy())
        .Fetch(fetchExpression);

}

I'm using Linq Specs (http://linqspecs.codeplex.com).

For full details please see http://blogs.planetcloud.co.uk/mygreatdiscovery/post/Eager-loading-with-NHibernate-LINQ.aspx

However, as Dan pointed out in his comment, this does looks like a better way of abstracting such queries.



回答3:

What I did was something like this (sorry, C#): First, the interface:

IQueryable<T> All<T>(params Expression<Func<T, Object>> [] fetchPaths);

As for the implementation:

public IQueryable<T> All<T>(params Expression<Func<T, Object>> [] fetchPaths)
{
    var queryable = this.session.Query<T>();

    foreach (var fetchPath in fetchPaths)
    {
        queryable = queryable.Fetch(fetchPath);
    }

    return queryable;
}