Nhibernate .Fetch calls fail on a mocked session
Asked Answered
F

3

7

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!

Foreland answered 22/10, 2015 at 16:43 Comment(0)
F
6

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);
        }
    }
}
Foreland answered 22/10, 2015 at 16:43 Comment(0)
B
2

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;
    }
}
Barnardo answered 23/11, 2016 at 8:37 Comment(0)
L
1

I came up with a new workaround for this, because I didn't want to add a type check to every fetch call we used in production code or have to make changes around all the existing .Fetch calls.

The reason that .Fetch fails has to do with something internal to .NET that is rewriting the expression tree. I couldn't figure out exactly what was going on but it seemed to come down to the input given to the expression being .List<T> and it was checking the input matches using essentially typeof(IQueryable<T>).IsAssignableFrom(typeof(List<T>)) which returns false.

I had already been using a wrapper instead of returning a plain List<T> (taken from this answer) to fix an issue with .ToFuture() not working in unit tests.

From the linked answer code I added the following to ExpressionTreeModifier:

protected override Expression VisitMethodCall(MethodCallExpression node)
{
    //Don't overwrite if fetch wasn't the method being called
    if (!node.Method.Name.Equals(nameof(EagerFetchingExtensionMethods.Fetch))
        && !node.Method.Name.Equals(nameof(EagerFetchingExtensionMethods.FetchMany))
        && !node.Method.Name.Equals(nameof(EagerFetchingExtensionMethods.ThenFetch))
        && !node.Method.Name.Equals(nameof(EagerFetchingExtensionMethods.ThenFetchMany)))
    {
        return base.VisitMethodCall(node);
    }

    //Get the first argument to the Fetch call. This would be our IQueryable or an Expression which returns the IQueryable.
    var fetchInput = node.Arguments[0];

    Expression returnExpression;

    switch (fetchInput.NodeType)
    {
        case ExpressionType.Constant:
            //If the input was a constant we need to run it through VisitConstant to get the underlying queryable from NHibernateQueryableProxy if applicable
            returnExpression = VisitConstant((ConstantExpression)fetchInput);
            break;
        default:
            //For everything else just return the input to fetch
            //This is covers cases if we do something like .Where(x).Fetch(x), here fetchInput would be another method call
            returnExpression = fetchInput;
            break;
    }

    return returnExpression;
}

What this does is rewrite the expression before it gets executed and completely removes the Fetch calls so we never end up with the Linq internals from trying to call it.

Loth answered 14/1, 2020 at 13:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.