Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Nhibernate .Fetch calls fail on a mocked session

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!

like image 357
viggity Avatar asked Oct 22 '15 16:10

viggity


2 Answers

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);
        }
    }
}
like image 57
viggity Avatar answered Oct 20 '22 12:10

viggity


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;
    }
}
like image 23
Piotr Kwiatek Avatar answered Oct 20 '22 12:10

Piotr Kwiatek