I love NHibernate (and NHibernate.Linq). I don't prematurely optimize, but sometimes I'll hit a really nasty N+1 issue. The recommended fix for the N+1 is to use NH's Fetch
extension method.
The problem arises when I create a Mock of the ISession
. I'll create a List<User>
and set my mock to return the list whenever someone calls _session.Query<User>()
. When I add a Fetch
call to the query (i.e. _session.Query<User>().Fetch(u => u.Address)
, I get the following error message:
There is no method 'Fetch' on type 'NHibernate.Linq.EagerFetchingExtensionMethods'
that matches the specified arguments
NHibernate's fetch accepts a plain old IQueryable<T>
but tries to cast it as specific NH implementations and fails if it can't.
I would really like Fetch
to not error if it is called on a non-NH implementation (i.e. a list) and just be ignored so I can still use it in my unit tests. Help!
Well, I tried to implement this myself, but thank god I found someone who already did the legwork.
http://mycodinglife.blog.com/2013/06/10/fetch-good-boy-now-play-nice-with-my-unit-tests/#
The only thing you have to do is call EagerlyFetch
instead of just Fetch
.
I've copied the relevant code below because his blog already has a fair amount of http 500 errors and css issues. I don't think it is being maintained.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using NHibernate.Linq;
using Remotion.Linq;
namespace LittleFish.Persistence.Extensions
{
/// <summary>
/// Provides extension method wrappers for NHibernate methods
/// to allow consuming source code to avoid "using" NHibernate.
/// </summary>
public static class NHibernateExtensions
{
/// <summary>
/// Eager-loads a projection of the specified queryable,
/// referencing a mapped child object.
/// </summary>
public static IFetchRequest<T, TRel> EagerlyFetch<T, TRel>(
this IQueryable<T> queryable,
Expression<Func<T, TRel>> expression)
{
if (queryable is QueryableBase<T>)
return FetchHelper.Create(queryable.Fetch(expression));
else
return FetchHelper.CreateNonNH<T, TRel>(queryable);
}
/// <summary>
/// Eager-loads a second-level projection of the specified queryable,
/// referencing a mapped child of the first eager-loaded child.
/// </summary>
public static IFetchRequest<T, TRel2> ThenEagerlyFetch<T, TRel, TRel2>(
this IFetchRequest<T, TRel> queryable,
Expression<Func<TRel, TRel2>> expression)
{
if (queryable is QueryableFetchHelper<T, TRel>)
return FetchHelper.CreateNonNH<T, TRel2>(queryable);
else
return FetchHelper.Create(queryable.ThenFetch(expression));
}
/// <summary>
/// Eager-loads a projection of the specified queryable,
/// referencing a mapped child object.
/// </summary>
public static IFetchRequest<T, TRel> EagerlyFetchMany<T, TRel>(
this IQueryable<T> queryable,
Expression<Func<T, IEnumerable<TRel>>> expression)
{
if(queryable is QueryableBase<T>)
return FetchHelper.Create(queryable.FetchMany(expression));
else
return FetchHelper.CreateNonNH<T, TRel>(queryable);
}
/// <summary>
/// Eager-loads a second-level projection of the specified queryable,
/// referencing a mapped child of the first eager-loaded child.
/// </summary>
public static IFetchRequest<T, TRel2> ThenEagerlyFetchMany
<T, TRel, TRel2>(
this IFetchRequest<T, TRel> queryable,
Expression<Func<TRel, IEnumerable<TRel2>>> expression)
{
if (queryable is QueryableFetchHelper<T, TRel>)
return FetchHelper.CreateNonNH<T, TRel2>(queryable);
else
return FetchHelper.Create(queryable.ThenFetchMany(expression));
}
}
/// <summary>
/// Provides a wrapper for NHibernate's FetchRequest interface,
/// so libraries that run eager-loaded queries don't have to reference
/// NHibernate assemblies.
/// </summary>
public interface IFetchRequest<TQuery, TFetch> :
INhFetchRequest<TQuery, TFetch>
{
}
internal class NhFetchHelper<TQuery, TFetch> : IFetchRequest<TQuery, TFetch>
{
private readonly INhFetchRequest<TQuery, TFetch> realFetchRequest;
//this is the real deal for NHibernate queries
internal NhFetchHelper(INhFetchRequest<TQuery, TFetch> realFetchRequest)
{
this.realFetchRequest = realFetchRequest;
}
public IEnumerator<TQuery> GetEnumerator()
{
return (realFetchRequest).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return (realFetchRequest).GetEnumerator();
}
public Expression Expression
{
get { return (realFetchRequest).Expression; }
}
public Type ElementType
{
get { return (realFetchRequest).ElementType; }
}
public IQueryProvider Provider
{
get { return (realFetchRequest).Provider; }
}
}
internal class QueryableFetchHelper<TQuery, TFetch> :
IFetchRequest<TQuery, TFetch>
{
private readonly IQueryable<TQuery> queryable;
//for use against non-NH datastores
internal QueryableFetchHelper(IQueryable<TQuery> queryable)
{
this.queryable = queryable;
}
public IEnumerator<TQuery> GetEnumerator()
{
return (queryable).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return (queryable).GetEnumerator();
}
public Expression Expression
{
get { return (queryable).Expression; }
}
public Type ElementType
{
get { return (queryable).ElementType; }
}
public IQueryProvider Provider
{
get { return (queryable).Provider; }
}
}
/// <summary>
/// The static "front door" to FetchHelper, with generic factories allowing
/// generic type inference.
/// </summary>
internal static class FetchHelper
{
public static NhFetchHelper<TQuery, TFetch> Create<TQuery, TFetch>(
INhFetchRequest<TQuery, TFetch> nhFetch)
{
return new NhFetchHelper<TQuery, TFetch>(nhFetch);
}
public static NhFetchHelper<TQuery, TFetch> Create<TQuery, TFetch>(
IFetchRequest<TQuery, TFetch> nhFetch)
{
return new NhFetchHelper<TQuery, TFetch>(nhFetch);
}
public static IFetchRequest<TQuery, TRel> CreateNonNH<TQuery, TRel>(
IQueryable<TQuery> queryable)
{
return new QueryableFetchHelper<TQuery, TRel>(queryable);
}
}
}
Fetch is an extension method which comes from NHibernate.Linq.EagerFetchingExtensionMethods
and that is why You can not mock it. If you accept the modification of the original production code, You can use a wrapper. Wrapper is the code which You will further mock!
Instead of calling Fetch in fluent way (query.Fetch(...)
), You can call a wrapper and inject query as a reference:
NHibernateExtensionsWrapper.Fetch(query, x => x.ChildTable).ToList();
How to implement this wrapper?
public class NHibernateExtensionsWrapper : INHibernateExtensionsWrapper
{
public INhFetchRequest<TOriginating, TRelated> Fetch<TOriginating, TRelated>(IQueryable<TOriginating> query,
Expression<Func<TOriginating, TRelated>> relatedObjectSelector)
{
return query.Fetch(relatedObjectSelector);
}
}
How to implement a wrapper mock?
public class NHibernateExtensionsWrapperMock : INHibernateExtensionsWrapper
{
public INhFetchRequest<TOriginating, TRelated> Fetch<TOriginating, TRelated>(IQueryable<TOriginating> query, Expression<Func<TOriginating, TRelated>> relatedObjectSelector)
{
return (INhFetchRequest<TOriginating, TRelated>) new NhFetchRequest<TOriginating, TRelated>(query);
}
private class NhFetchRequest<TOriginating, TRelated> : INhFetchRequest<TOriginating, TRelated>
{
private readonly IQueryable<TOriginating> _query;
public NhFetchRequest(IQueryable<TOriginating> query)
{
_query = query;
}
public IEnumerator<TOriginating> GetEnumerator()
{
return _query.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public Expression Expression => _query.Expression;
public Type ElementType => _query.ElementType;
public IQueryProvider Provider => _query.Provider;
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With